Warum sind wahr und falsch so groß?

80

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 trueund 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 trueim Grunde genommen gerecht ist exit 0und falseist 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

Kidburla
quelle
9
Sie sollten command -V truenicht verwenden which. Es wird ausgegeben: true is a shell builtinfür Bash.
MEUH
32
trueund false 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. whichIgnoriert eingebaute Befehle und sucht nur nach externen Befehlen. Aus diesem Grund wurden nur die externen Befehle angezeigt. Versuchen Sie type -a trueund type -a falsestattdessen.
mtraceur
74
Es ist ironisch , dass Sie eine so lange Frage schreiben zu sagen : „Warum sind trueund falsejeden 29KB? Was ist als der Return - Code in den ausführbar andere ist?“
David Richerby
7
Einige frühe Versionen von Unix hatten nur eine leere Datei für true, da dies ein gültiges sh-Programm war, das den Exit-Code 0 zurückgab. Ich wünschte wirklich, ich könnte einen Artikel finden, den ich vor Jahren über die Geschichte des wahren Dienstprogramms von einer leeren Datei bis gelesen habe Die Monstrosität, die es heute ist, aber alles, was ich finden konnte, ist diese: trillian.mit.edu/~jc/humor/ATT_Copyright_true.html
Philip
9
Obligatorisch - die kleinste Implementierung von false: muppetlabs.com/~breadbox/software/tiny/teensy.html
d33tah

Antworten:

117

In der Vergangenheit /bin/trueund /bin/falsein der Shell waren eigentlich Skripte.

Zum Beispiel in einem 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
$  

Zumindest in der heutigen Zeit bashwerden die Befehle trueund falseals eingebaute Shell-Befehle implementiert. Daher werden standardmäßig keine ausführbaren Binärdateien aufgerufen, sowohl bei Verwendung der Anweisungen falseund truein der bashBefehlszeile als auch innerhalb von Shell-Skripten.

Aus der bashQuelle 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
    };

Auch per @meuh Kommentare:

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

Man kann also mit hoher Sicherheit sagen, dass die trueund falseausführbaren Dateien hauptsächlich zum Aufrufen aus anderen Programmen existieren .

Von nun an konzentriert sich die Antwort auf die /bin/trueBinärdatei aus dem coreutilsPaket in Debian 9/64-Bit. ( /usr/bin/trueRedHat wird ausgeführt. RedHat und Debian verwenden beide das coreutilsPaket, analysierten die kompilierte Version des letzteren und hatten es mehr zur Hand).

Wie in der Quelldatei zu sehen ist false.c, /bin/falsewird 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.

#define EXIT_STATUS EXIT_FAILURE
#include "true.c"

Wie es auch von beiden ausführbaren Dateien mit der gleichen Größe bestätigt werden kann:

$ 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

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 die bashLeistung nicht wesentlich und werden von bash(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 falseund verwenden true, da er in Distributionen mit nur noch zweimal bereitgestellt wird coreutils.

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/trueist 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 trueDienstprogramm bekam in der Tat mehr cruft Code im Laufe der Jahre, vor allem den Standard - Support für --versionund --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 coreutilsBinärdateien verwendet wird, die als statische Bibliothek verknüpft sind. Die Metada zum Erstellen einer elfausfü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/trueausführbaren Binärdatei detailliert beschrieben ist, und wie wir zu dieser Schlussfolgerung gekommen sind.

bintrue bintrue2

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 coreutilsBibliotheken enthält)

Wir können die verschiedenen Bausteine ​​der /bin/trueBinärdatei hier sehen (Debian 9 - 64 Bits von 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

Von diesen:

  • Text (normalerweise Code) ist ungefähr 24 KB groß
  • Daten (initialisierte Variablen, meistens Strings) sind ca. 1 KB groß
  • bss (nicht initialisierte Daten) 0,5 KB

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 truewir, dass die Binärdatei 26159 Bytes umfasst, der tatsächlich kompilierte Code 13017 Bytes und der Rest verschiedene Daten / Initialisierungscodes enthält.

Es true.cist jedoch nicht die ganze Geschichte und 13 KB scheinen ziemlich übertrieben, wenn es nur diese Datei wäre; wir können aufgerufene main()Funktionen sehen, die nicht in den externen Funktionen aufgelistet sind, die in der Elfe mit zu sehen sind objdump -T true; Funktionen, die vorhanden sind bei:

Die nicht extern eingebundenen Zusatzfunktionen main()sind:

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

Mein erster Verdacht war also teilweise richtig, während die Bibliothek dynamische Bibliotheken verwendet, ist die /bin/trueBinä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:

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);
}

Die Dezimalgröße der verschiedenen Abschnitte der Binärdatei:

$ 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

Ausgabe von 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)

Ausgabe von objdump -T true(externe Funktionen zur Laufzeit dynamisch verknüpft)

$ 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
Rui F Ribeiro
quelle
5
Nachdem in letzter Zeit einige Programmierungen mit einem 64kB + 2kB Mikrocontroller durchgeführt wurden, scheinen 28kB nicht allzu klein zu sein.
Barleyman
1
@Barleyman Sie haben OpenWRT, yocto, uClinux, uclib, busybox, Microcoreutils und andere Lösungen für diese Art von Umgebungen. Bearbeitet den Beitrag mit Ihrem Anliegen.
Rui F Ribeiro
4
@Barleyman: Wenn Sie für die Größe der ausführbaren Binärdatei optimiert haben, können Sie eine ausführbare x86-ELF-Datei mit 45 Byte implementieren trueund falseden 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)
Peter Cordes
3
Nicht wirklich, nein. Yocto zum Beispiel kann auf weniger als ein Megabyte beschränkt sein, was Haufen und Grenzen von mehr als 64 kB entspricht. Bei dieser Art von Gerät können Sie RTOS mit rudimentärer Prozess- / Speicherverwaltung verwenden, aber auch diese können leicht zu schwer werden. Ich habe ein einfaches kooperatives Multithreading-System geschrieben und den eingebauten Speicherschutz verwendet, um den Code vor dem Überschreiben zu schützen. Insgesamt verbraucht die Firmware derzeit etwa 55 KB, sodass dort nicht zu viel Platz für zusätzlichen Overhead vorhanden ist. Diese ginormösen 2kB Look-Up-Tabellen ..
Barleyman
2
@PeterCordes sicher, aber Sie brauchen ein paar Größenordnungen mehr Ressourcen, bevor Linux rentabel wird. Für das, was es wert ist, funktioniert C ++ auch in dieser Umgebung nicht wirklich. Nun, sowieso nicht die Standardbibliotheken. Iostream ist gleich bei ca. 200kB etc.
Barleyman
34

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 trueselbst zu kompilieren , und Sie werden feststellen, dass sie bereits einige KB groß ist. Zum Beispiel auf meinem System:

$ echo 'int main() { return 0; }' | gcc -xc - -o true
$ wc -c true
8136 true

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 --helpoder /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

Maks Verver
quelle
2
FYI war ich über diese coreutils Implementierungen auf ihren Bug - Tracker beschweren, aber keine Chance, es fest lists.gnu.org/archive/html/bug-coreutils/2016-03/msg00040.html
rudimeier
7
Es ist nicht die Logik für Argumente, C ist nicht so ineffizient ... es sind Inline-Bibliotheken / Haushaltsaufgaben. Schauen Sie sich meine Antwort für die blutigen Details an.
Rui F Ribeiro
8
Dies ist irreführend, da es darauf hindeutet, dass kompilierter Maschinencode (von C oder auf andere Weise) den enormen Speicherplatz beansprucht - der tatsächliche Overhead hat mehr mit massiven Mengen an Standard-C-Bibliotheks- / Laufzeit-Boilerplates zu tun, die vom Compiler in eingebettet werden um mit der C-Bibliothek zusammenzuarbeiten (glibc, es sei denn, Sie haben gehört, dass Ihr System wahrscheinlich etwas anderes verwendet), und in geringerem Umfang mit ELF-Headern / Metadaten (von denen viele nicht unbedingt erforderlich sind, aber als sinnvoll genug erachtet werden in Standardbuilds aufnehmen).
mtraceur
2
Die tatsächlichen Werte für main () + usage () + strings für beide Funktionen betragen etwa 2 KB, nicht 20 KB.
Rui F Ribeiro
2
@JdeBP Logik für --version / version Funktionen 1KB, --usage / - help 833 Bytes, main () 225 Bytes und die gesamten statischen Daten der Binärdatei sind 1KB
Rui F Ribeiro
25

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/truedies 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).

$ 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
$

Oder erstellen Sie mit einer etwas fiesen / genialen Herangehensweise (ein dickes Lob an stalkr ) Ihre eigenen ELF-Header, um sie auf 132 bis 127 Byte zu reduzieren . Wir betreten hier das Gebiet von Code Golf .

$ 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
$
Steve
quelle
2
Kommentare sind nicht für eine längere Diskussion gedacht. Diese Unterhaltung wurde in den Chat verschoben .
Terdon
2
Sehen Sie sich auch diese hervorragende Zusammenfassung an: muppetlabs.com/~breadbox/software/tiny/teensy.html
mic_e
3
Sie verwenden das int 0x8032-Bit-ABI in einer ausführbaren 64-Bit-Datei, was ungewöhnlich ist, aber unterstützt wird . Mit syscallwürden Sie nichts sparen. Die hohen Bytes von ebxwerden ignoriert, sodass Sie 2 Byte verwenden können mov bl,1. Oder natürlich xor ebx,ebxfür Null . Linux inits Integer-Register auf Null, so dass Sie nur 1 = __NR_exit (i386 ABI) erhalten könnteninc eax .
Peter Cordes
1
Ich habe den Code in Ihrem Golf-Beispiel aktualisiert, um den 64-Bit-ABI zu verwenden, und habe ihn auf 127 Byte reduziert true. (Ich sehe jedoch keine einfache Möglichkeit, weniger als 128 Bytes zu verwalten false, als die 32-Bit-ABI zu verwenden oder die Tatsache auszunutzen, dass sich Linux-Nullen beim Start des Prozesses registrieren, sodass mov al,252(2 Bytes) funktioniert. push imm8/ pop rdiWould funktioniert auch anstelle von leazum Einstellen edi=1, aber wir können immer noch nicht die 32-Bit-ABI schlagen, wo wir mov bl,1ohne ein REX-Präfix könnten .
Peter Cordes
2
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

Ziemlich groß auf meinem Ubuntu 16.04 auch. genau die gleiche Größe? Was macht sie so groß?

strings $(which true)

(Auszug:)

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, es gibt Hilfe für wahr und falsch, also lasst es uns versuchen:

true --help 
true --version
#

Nichts. Ah, da war diese andere Zeile:

NOTE: your shell may have its own version of %s, which usually supersedes
    the version described here.

Auf meinem System ist es also / bin / true, nicht / 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.

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.

Benutzer unbekannt
quelle
Einschließlich statischer Bibliotheken und der Hälfte der Binärgröße für Elf Metada. Siehe meine Antwort.
Rui F Ribeiro