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 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 qui m'a le plus surpris, c'est leur taille. Je m'attendais à ce qu'ils ne soient que quelques octets chacun, ce qui true
est fondamentalement juste exit 0
et qui l' 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, à 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
la source
command -V true
paswhich
. Il va sortir:true is a shell builtin
pour bash.true
etfalse
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.which
ignore les commandes intégrées et ne recherche que les commandes externes. C'est pourquoi il ne vous a montré que les commandes externes. Essayeztype -a true
et à latype -a false
place.true
ilfalse
29 ko chacun? Que contient l'exécutable à part le code de retour?"false
: muppetlabs.com/~breadbox/software/tiny/teensy.htmlRéponses:
Dans le passé
/bin/true
et/bin/false
dans le shell, il y avait en fait des scripts.Par exemple, dans un PDP / 11 Unix System 7:
De nos jours, au moins dans
bash
, les commandestrue
etfalse
sont 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 directivesfalse
ettrue
dans labash
ligne de commande ou à l'intérieur de scripts shell.De la
bash
sourcebuiltins/mkbuiltins.c
:Aussi par commentaires @meuh:
Donc , on peut dire avec un degré élevé de certitude les
true
et lesfalse
fichiers exécutables existent principalement pour être appelé d'autres programmes .À partir de maintenant, la réponse portera sur le
/bin/true
fichier binaire ducoreutils
paquet dans Debian 9/64 bits. (/usr/bin/true
sous RedHat. RedHat et Debian utilisent lecoreutils
paquet, 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/false
est 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.Comme il peut également être confirmé par les deux exécutables ayant la même taille:
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 à labash
performance, ils ne sont plus utilisés parbash
(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
false
ettrue
, comme il vient d'être déployé deux fois dans les distributionscoreutils
.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/true
est 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'
true
utilitaire a en effet obtenu plus de code cruel au fil des ans, notamment le support standard pour--version
et--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
coreutils
binaires liés en tant que bibliothèque statique. La méta pour construire unelf
fichier 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/true
fichier binaire exécutable et comment nous en sommes arrivés à cette conclusion.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
coreutils
bibliothèques)Nous pouvons voir les différents blocs de construction du
/bin/true
binaire ici (Debian 9 - 64 bits depuiscoreutils
):De celles:
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.c
l’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 dedansmain()
qui ne sont pas listées dans les fonctions externes vues dans l'elfe avecobjdump -T true
; fonctions présentes à:Les fonctions supplémentaires non liées en externe
main()
sont:Donc , mon premier soupçon était en partie correcte, tandis que la bibliothèque utilise les bibliothèques dynamiques, le
/bin/true
binaire 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:La taille décimale des différentes sections du binaire:
Sortie de
readelf -S true
Sortie de
objdump -T true
(fonctions externes liées dynamiquement au moment de l'exécution)la source
true
oufalse
avec 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)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
true
vous-même, et vous remarquerez que sa taille est déjà réduite à quelques Ko. Par exemple, sur mon système: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 --help
ou/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
la source
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/true
cela 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).
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 à
132127 octets. Nous entrons dans le territoire de Code Golf ici.la source
int 0x80
ABI 32 bits dans un exécutable 64 bits, ce qui est inhabituel mais pris en charge . L'utilisationsyscall
ne vous sauverait rien. Les octets hauts deebx
sont ignorés, vous pouvez donc utiliser 2 octetsmov bl,1
. Ou bien surxor ebx,ebx
pour zéro . Linux écrit son nombre entier à zéro, vous pouvez donc simplementinc eax
obtenir 1 = __NR_exit (ABI i386).true
. (Je ne vois pas un moyen facile de gérer moins de 128 octets pourfalse
, 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, doncmov al,252
(2 octets) fonctionne.push imm8
/pop rdi
Serait fonctionne également au lieu delea
pour le réglageedi=1
, mais nous ne pouvons toujours pas battre le ABI 32 bits où nous pourrionsmov bl,1
sans préfixe REXAssez gros sur mon Ubuntu 16.04 aussi. exactement la même taille? Qu'est-ce qui les rend si gros?
(extrait:)
Ah, il y a de l'aide pour le vrai et le faux, alors essayons-le:
Rien. Ah, il y avait cette autre ligne:
Donc sur mon système, c'est / bin / true, pas / usr / bin / true
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.
la source