Dopo aver scoperto che diversi comandi comuni (come read
) sono in realtà Bash builtin (e quando li eseguo al prompt sto effettivamente eseguendo uno script di shell a due righe che inoltra solo al builtin), stavo cercando di vedere se lo stesso vale per true
e 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
è fondamentalmente solo exit 0
e 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 mia sorpresa che entrambi i file hanno una dimensione superiore 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 oltre al codice di ritorno?
PS:sto usando RHEL 7.4
Risposta accettata:
In passato, /bin/true
e /bin/false
nella shell c'erano in realtà 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
, il true
e false
i comandi sono implementati come comandi incorporati nella shell. Quindi nessun file binario eseguibile viene invocato per impostazione predefinita, sia quando si utilizza il false
e true
direttive in bash
riga di comando e all'interno degli script della shell.
Dalla bash
sorgente, 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 il true
e false
i file eseguibili esistono principalmente per essere chiamati da altri programmi .
D'ora in poi, la risposta si concentrerà sul /bin/true
binario da coreutils
pacchetto in Debian 9/64 bit. (/usr/bin/true
eseguendo RedHat. RedHat e Debian usano entrambe le coreutils
pacchetto, analizzato la versione compilata di quest'ultimo avendola più a portata di mano).
Come si può vedere nel file sorgente false.c
, /bin/false
è compilato con (quasi) lo stesso codice sorgente di /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ò essere confermato anche da entrambi gli eseguibili aventi la stessa dimensione:
$ 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
Ahimè, la domanda diretta alla risposta why are true and false so large?
potrebbe essere, perché non ci sono più ragioni così pressanti per preoccuparsi delle loro massime prestazioni. Non sono essenziali per bash
performance, non più utilizzato da bash
(sceneggiatura).
Commenti simili si applicano alle loro dimensioni, 26 KB per il tipo di hardware che abbiamo al giorno d'oggi è insignificante. Lo spazio non è più prezioso per il tipico server/desktop e non si preoccupano nemmeno più di utilizzare lo stesso binario per false
e true
, poiché viene distribuito due volte nelle distribuzioni che utilizzano coreutils
.
Concentrandoci, tuttavia, nel vero spirito della domanda, perché qualcosa che dovrebbe essere così semplice e piccolo diventa così grande?
La distribuzione reale delle sezioni di /bin/true
è come mostrano questi grafici; il codice principale+dati ammonta a circa 3 KB su un binario da 26 KB, che equivale al 12% della dimensione di /bin/true
.
Il true
utility ha ottenuto davvero più codice cruft nel corso degli anni, in particolare il supporto standard per --version
e --help
.
Tuttavia, che non è la (unica) giustificazione principale per essere così grande, ma piuttosto, pur essendo collegato dinamicamente (usando librerie condivise), avendo anche parte di una libreria generica comunemente usata da coreutils
binari collegati come una libreria statica. Il metada per costruire un elf
il file eseguibile rappresenta anche una parte significativa del file binario, essendo un file relativamente piccolo per gli standard odierni.
Il resto della risposta serve a spiegare come siamo arrivati a costruire i seguenti grafici che descrivono in dettaglio la composizione di /bin/true
file binario eseguibile e come siamo arrivati a tale conclusione.
Come dice @Maks, il binario è stato compilato da C; come anche dal mio commento, è anche confermato che proviene da coreutils. Stiamo puntando direttamente agli autori git https://github.com/wertarbyte/coreutils/blob/master/src/true.c, invece di gnu git come @Maks (stesse fonti, repository diversi:questo repository è stato selezionato in quanto ha il sorgente completo di coreutils
biblioteche)
Possiamo vedere i vari elementi costitutivi di /bin/true
binario qui (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 questi:
- il testo (di solito il codice) è di circa 24 KB
- i dati (variabili inizializzate, principalmente stringhe) sono circa 1 KB
- bss (dati non inizializzati) 0,5 KB
Dei 24 KB, circa 1 KB serve per riparare le 58 funzioni esterne.
Ciò lascia ancora circa 23 KB per il resto del codice. Mostreremo di seguito che il file principale effettivo - il codice main()+usage() è compilato di circa 1 KB e spiegheremo a cosa servono gli altri 22 KB.
Esaminando 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 inizializzazione/dati assortiti.
Tuttavia, true.c
non è tutta la storia e 13 KB sembrano piuttosto eccessivi se fosse solo quel file; possiamo vedere le funzioni chiamate in main()
che non sono elencate nelle funzioni esterne viste nell'elfo con objdump -T true
; funzioni presenti in:
- 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
Quelle funzioni extra non collegate esternamente in main()
sono:
- set_program_name()
- close_stdout()
- versione_etc()
Quindi il mio primo sospetto era in parte corretto, mentre la libreria utilizza librerie dinamiche, il /bin/true
binary è grande *perché ne ha alcuni librerie statiche incluse* (ma non è l'unica causa).
La compilazione del codice C di solito non è quello inefficiente per avere tale spazio non contabilizzato, da qui il mio sospetto iniziale che qualcosa non andasse.
Lo spazio extra, quasi il 90% della dimensione del file binario, è costituito da librerie/metadati elfi extra.
Mentre si utilizza Hopper per disassemblare/decompilare il binario per capire dove si trovano le funzioni, si può vedere che il codice binario compilato della funzione true.c/usage() è in realtà 833 byte e della funzione true.c/main() è 225 byte, che è all'incirca leggermente inferiore a 1 KB. La logica per le funzioni di versione, che è sepolta nelle librerie statiche, è di circa 1 KB.
L'effettiva compilazione main()+usage()+version()+strings+vars utilizza solo da 3 KB a 3,5 KB circa.
È davvero ironico che tali piccole e umili utility siano diventate di dimensioni maggiori per i motivi spiegati 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
Output 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