Nachdem ich herausgefunden hatte, dass es sich bei mehreren allgemeinen Befehlen (wie z. B. read
) tatsächlich um Bash-Builtins handelt (und wenn ich sie an der Eingabeaufforderung ausführe, führe ich tatsächlich ein zweizeiliges Shell-Skript aus, das direkt an das Builtin weiterleitet), habe ich versucht, zu überprüfen, ob dies auch der Fall ist ist wahr für true
und false
.
Nun, sie sind definitiv Binärdateien.
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$
Am überraschendsten fand ich jedoch ihre Größe. Ich habe erwartet, dass sie jeweils nur ein paar Bytes groß sind, was true
im Grunde genommen gerecht ist exit 0
und false
ist exit 1
.
sh-4.2$ true
sh-4.2$ echo $?
0
sh-4.2$ false
sh-4.2$ echo $?
1
sh-4.2$
Zu meiner Überraschung stellte ich jedoch fest, dass beide Dateien eine Größe von über 28 KB haben.
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$
Meine Frage lautet also: Warum sind sie so groß? Was ist in der ausführbaren Datei anders als der Rückkehrcode?
PS: Ich benutze RHEL 7.4
quelle
command -V true
nicht verwendenwhich
. Es wird ausgegeben:true is a shell builtin
für Bash.true
undfalse
sind builtins in jeder modernen Schale, aber die Systeme auch beinhaltet externe Programmversionen von ihnen , weil sie Teil des Standardsystems ist so , dass Programme Befehle direkt ( unter Umgehung der Shell) aufrufen können sie benutzen.which
Ignoriert eingebaute Befehle und sucht nur nach externen Befehlen. Aus diesem Grund wurden nur die externen Befehle angezeigt. Versuchen Sietype -a true
undtype -a false
stattdessen.true
undfalse
jeden 29KB? Was ist als der Return - Code in den ausführbar andere ist?“false
: muppetlabs.com/~breadbox/software/tiny/teensy.htmlAntworten:
In der Vergangenheit
/bin/true
und/bin/false
in der Shell waren eigentlich Skripte.Zum Beispiel in einem PDP / 11 Unix System 7:
Zumindest in der heutigen Zeit
bash
werden die Befehletrue
undfalse
als eingebaute Shell-Befehle implementiert. Daher werden standardmäßig keine ausführbaren Binärdateien aufgerufen, sowohl bei Verwendung der Anweisungenfalse
undtrue
in derbash
Befehlszeile als auch innerhalb von Shell-Skripten.Aus der
bash
Quellebuiltins/mkbuiltins.c
:Auch per @meuh Kommentare:
Man kann also mit hoher Sicherheit sagen, dass die
true
undfalse
ausführbaren Dateien hauptsächlich zum Aufrufen aus anderen Programmen existieren .Von nun an konzentriert sich die Antwort auf die
/bin/true
Binärdatei aus demcoreutils
Paket in Debian 9/64-Bit. (/usr/bin/true
RedHat wird ausgeführt. RedHat und Debian verwenden beide dascoreutils
Paket, analysierten die kompilierte Version des letzteren und hatten es mehr zur Hand).Wie in der Quelldatei zu sehen ist
false.c
,/bin/false
wird mit (fast) demselben Quellcode kompiliert wie/bin/true
, indem stattdessen nur EXIT_FAILURE (1) zurückgegeben wird, sodass diese Antwort für beide Binärdateien angewendet werden kann.Wie es auch von beiden ausführbaren Dateien mit der gleichen Größe bestätigt werden kann:
Leider könnte die direkte Frage zur Antwort
why are true and false so large?
lauten, denn es gibt nicht mehr so dringende Gründe, sich um ihre Spitzenleistung zu kümmern. Sie sind für diebash
Leistung nicht wesentlich und werden vonbash
(scripting) nicht mehr verwendet .Ähnliche Kommentare gelten für ihre Größe, 26 KB für die Art von Hardware, die wir heutzutage haben, sind unbedeutend. Speicherplatz ist für den typischen Server / Desktop nicht mehr knapp und sie müssen nicht einmal mehr dieselbe Binärdatei für
false
und verwendentrue
, da er in Distributionen mit nur noch zweimal bereitgestellt wirdcoreutils
.Fokussiert man sich jedoch im eigentlichen Sinne auf die Frage, warum etwas, das so einfach und klein sein sollte, so groß wird?
Die tatsächliche Verteilung der Abschnitte von
/bin/true
ist wie in diesen Diagrammen dargestellt. Der Hauptcode + Daten beläuft sich auf ungefähr 3 KB aus einer 26 KB großen Binärdatei, was 12% der Größe von entspricht/bin/true
.Das
true
Dienstprogramm bekam in der Tat mehr cruft Code im Laufe der Jahre, vor allem den Standard - Support für--version
und--help
.Dies ist jedoch nicht der (einzige) Hauptgrund dafür, dass es so umfangreich ist, sondern dass es zwar dynamisch verknüpft ist (mithilfe von gemeinsam genutzten Bibliotheken), aber auch Teil einer generischen Bibliothek ist, die üblicherweise von
coreutils
Binärdateien verwendet wird, die als statische Bibliothek verknüpft sind. Die Metada zum Erstellen einerelf
ausführbaren Datei macht auch einen erheblichen Teil der Binärdatei aus, da sie nach heutigen Maßstäben relativ klein ist.Der Rest der Antwort dient der Erläuterung, wie wir die folgenden Diagramme erstellen mussten, in denen die Zusammensetzung der
/bin/true
ausführbaren Binärdatei detailliert beschrieben ist, und wie wir zu dieser Schlussfolgerung gekommen sind.Wie @Maks sagt, wurde die Binärdatei aus C kompiliert. Nach meinem Kommentar wurde auch bestätigt, dass es von Coreutils stammt. Wir verweisen direkt auf den (die) Autor (en) git https://github.com/wertarbyte/coreutils/blob/master/src/true.c anstelle des gnu-git als @Maks (gleiche Quellen, verschiedene Repositorys - dieses Repository) wurde ausgewählt, da es die vollständige Quelle der
coreutils
Bibliotheken enthält)Wir können die verschiedenen Bausteine der
/bin/true
Binärdatei hier sehen (Debian 9 - 64 Bits voncoreutils
):Von diesen:
Von den 24KB dient etwa 1KB zur Einrichtung der 58 externen Funktionen.
Für den Rest des Codes verbleiben noch ungefähr 23 KB. Wir werden unten zeigen, dass der eigentliche Code der Hauptdatei - main () + usage () - etwa 1 KB groß ist, und erklären, wofür die anderen 22 KB verwendet werden.
Weiter unten in der Binärdatei mit sehen
readelf -S true
wir, dass die Binärdatei 26159 Bytes umfasst, der tatsächlich kompilierte Code 13017 Bytes und der Rest verschiedene Daten / Initialisierungscodes enthält.Es
true.c
ist jedoch nicht die ganze Geschichte und 13 KB scheinen ziemlich übertrieben, wenn es nur diese Datei wäre; wir können aufgerufenemain()
Funktionen sehen, die nicht in den externen Funktionen aufgelistet sind, die in der Elfe mit zu sehen sindobjdump -T true
; Funktionen, die vorhanden sind bei:Die nicht extern eingebundenen Zusatzfunktionen
main()
sind:Mein erster Verdacht war also teilweise richtig, während die Bibliothek dynamische Bibliotheken verwendet, ist die
/bin/true
Binärdatei groß *, weil sie einige statische Bibliotheken enthält * (aber das ist nicht die einzige Ursache).Das Kompilieren von C-Code ist normalerweise nicht so ineffizient, wenn man solchen Speicherplatz nicht berücksichtigt, daher war mein anfänglicher Verdacht falsch.
Der zusätzliche Speicherplatz, fast 90% der Größe der Binärdatei, besteht in der Tat aus zusätzlichen Bibliotheken / Elf-Metadaten.
Während der Verwendung von Hopper zum Zerlegen / Dekompilieren der Binärdatei, um zu verstehen, wo sich Funktionen befinden, ist ersichtlich, dass der kompilierte Binärcode der Funktion true.c / usage () tatsächlich 833 Byte und der der Funktion true.c / main () 225 Byte beträgt Bytes, was ungefähr etwas weniger als 1 KB ist. Die Logik für Versionsfunktionen, die in den statischen Bibliotheken vergraben ist, beträgt ungefähr 1 KB.
Die tatsächlich kompilierten main () + usage () + version () + strings + vars belegen nur etwa 3 KB bis 3,5 KB.
Es ist in der Tat ironisch, dass solche kleinen und bescheidenen Versorgungsunternehmen aus den oben erläuterten Gründen größer geworden sind.
Verwandte Frage: Verstehen, was eine Linux-Binärdatei tut
true.c
main () mit den anstößigen Funktionsaufrufen:Die Dezimalgröße der verschiedenen Abschnitte der Binärdatei:
Ausgabe von
readelf -S true
Ausgabe von
objdump -T true
(externe Funktionen zur Laufzeit dynamisch verknüpft)quelle
true
undfalse
den ausführbaren Code (4 x86-Anweisungen) in den ELF-Programmheader packen (ohne Unterstützung für Befehlszeilenoptionen!). . Ein Wirbelwind-Tutorial zum Erstellen von wirklich teensy ELF Executables für Linux . (Oder etwas größer, wenn Sie je nach Implementierungsdetails des Linux-ELF-Loaders vermeiden möchten: P)Die Implementierung stammt wahrscheinlich von GNU coreutils. Diese Binärdateien werden aus C kompiliert. Es wurden keine besonderen Anstrengungen unternommen, um sie kleiner als standardmäßig zu machen.
Sie könnten versuchen, die triviale Implementierung von sich
true
selbst zu kompilieren , und Sie werden feststellen, dass sie bereits einige KB groß ist. Zum Beispiel auf meinem System:Natürlich sind Ihre Binärdateien noch größer. Das liegt daran, dass sie auch Befehlszeilenargumente unterstützen. Versuchen Sie es mit
/usr/bin/true --help
oder/usr/bin/true --version
.Zusätzlich zu den Zeichenfolgendaten enthält die Binärdatei eine Logik zum Parsen von Befehlszeilenflags usw. Dies summiert sich anscheinend auf etwa 20 KB Code.
Als Referenz finden Sie den Quellcode hier: http://git.savannah.gnu.org/cgit/coreutils.git/tree/src/true.c
quelle
Wenn Sie sie auf die Kernfunktionalität reduzieren und in Assembler schreiben, erhalten Sie weitaus kleinere Binärdateien.
Ursprüngliche True / False-Binärdateien sind in C geschrieben, das von Natur aus verschiedene Bibliotheks- und Symbolreferenzen einbezieht. Wenn Sie ausführen, ist
readelf -a /bin/true
dies ziemlich auffällig.352 Bytes für eine entfernte statische ELF-Programmdatei (mit Platz zum Speichern einiger Bytes durch Optimieren des Asms für die Codegröße).
Oder erstellen Sie mit einer etwas fiesen / genialen Herangehensweise (ein dickes Lob an stalkr ) Ihre eigenen ELF-Header, um sie auf
132 bis127 Byte zu reduzieren . Wir betreten hier das Gebiet von Code Golf .quelle
int 0x80
32-Bit-ABI in einer ausführbaren 64-Bit-Datei, was ungewöhnlich ist, aber unterstützt wird . Mitsyscall
würden Sie nichts sparen. Die hohen Bytes vonebx
werden ignoriert, sodass Sie 2 Byte verwenden könnenmov bl,1
. Oder natürlichxor ebx,ebx
für Null . Linux inits Integer-Register auf Null, so dass Sie nur 1 = __NR_exit (i386 ABI) erhalten könnteninc eax
.true
. (Ich sehe jedoch keine einfache Möglichkeit, weniger als 128 Bytes zu verwaltenfalse
, als die 32-Bit-ABI zu verwenden oder die Tatsache auszunutzen, dass sich Linux-Nullen beim Start des Prozesses registrieren, sodassmov al,252
(2 Bytes) funktioniert.push imm8
/pop rdi
Would funktioniert auch anstelle vonlea
zum Einstellenedi=1
, aber wir können immer noch nicht die 32-Bit-ABI schlagen, wo wirmov bl,1
ohne ein REX-Präfix könnten .Ziemlich groß auf meinem Ubuntu 16.04 auch. genau die gleiche Größe? Was macht sie so groß?
(Auszug:)
Ah, es gibt Hilfe für wahr und falsch, also lasst es uns versuchen:
Nichts. Ah, da war diese andere Zeile:
Auf meinem System ist es also / bin / true, nicht / usr / bin / true
Es gibt also Hilfe, es gibt Versionsinformationen, die für die Internationalisierung an eine Bibliothek gebunden sind. Dies erklärt einen Großteil der Größe, und die Shell verwendet ihren optimierten Befehl ohnehin und die meiste Zeit.
quelle