Dlaczego prawda i fałsz są tak duże?


80

Po odkryciu, że kilka typowych poleceń (takich jak read) jest w rzeczywistości wbudowanymi funkcjami Bash (a kiedy je uruchamiam w wierszu poleceń, faktycznie uruchamiam dwuwierszowy skrypt powłoki, który właśnie przekazuje do wbudowanej wersji), szukałem, czy to samo jest prawdziwe dla truei false.

Cóż, zdecydowanie są to pliki binarne.

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$

Najbardziej zaskoczyła mnie jednak ich wielkość. Spodziewałem się, że będą miały tylko kilka bajtów, co truejest w zasadzie słuszne exit 0i falsejest exit 1.

sh-4.2$ true
sh-4.2$ echo $?
0
sh-4.2$ false
sh-4.2$ echo $?
1
sh-4.2$

Zaskoczyłem jednak, że oba pliki mają rozmiar ponad 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$

Moje pytanie brzmi: dlaczego są takie duże? Co zawiera plik wykonywalny inny niż kod powrotu?

PS: Używam RHEL 7.4


9
Należy używać command -V truenie which. Wyjdzie: true is a shell builtindla bash.
Meuh

32
truei false wbudowane w każdą nowoczesną powłokę, ale systemy zawierają również ich zewnętrzne wersje programów, ponieważ jest to część standardowego systemu, dzięki czemu programy bezpośrednio wywołujące polecenia (omijając powłokę) mogą z nich korzystać. whichignoruje wbudowane i wyszukuje tylko polecenia zewnętrzne, dlatego pokazał tylko te zewnętrzne. Spróbuj type -a truei type -a falsezamiast tego.
mtraceur

74
Ironiczne jest to, że piszesz tak długie pytanie, aby powiedzieć „Dlaczego truei false29kb każdy? Co zawiera plik wykonywalny inny niż kod powrotu?”
David Richerby

7
Niektóre wczesne wersje unixa miały po prostu pusty plik dla prawdy, ponieważ był to prawidłowy program sh, który zwracałby kod wyjścia 0. Naprawdę chciałbym znaleźć artykuł, który czytałem przed laty o historii prawdziwego narzędzia od pustego pliku do potworność jest dzisiaj, ale wszystko, co mogłem znaleźć, to: trillian.mit.edu/~jc/humor/ATT_Copyright_true.html
Philip

9
Obowiązkowe - najmniejsza implementacja false: muppetlabs.com/~breadbox/software/tiny/teensy.html
d33tah

Odpowiedzi:


117

W przeszłości /bin/trueiw /bin/falsepowłoce znajdowały się skrypty.

Na przykład w systemie 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
$  

Obecnie, przynajmniej bash, gdy truei falsepolecenia są realizowane jako powłoki wbudowanych poleceń. W związku z tym domyślnie nie są wywoływane pliki wykonywalne, zarówno podczas korzystania z dyrektyw falsei truew bashwierszu poleceń, jak i wewnątrz skryptów powłoki.

Ze bashźródła builtins/mkbuiltins.c:

char * posix_builtins [] =
    {
      „alias”, „bg”, „cd”, „polecenie”, „** false **”, „fc”, „fg”, „getopts”, „jobs”,
      „kill”, „newgrp”, „pwd”, „read”, „** true **”, „umask”, „unalias”, „wait”,
      (char *) NULL
    };

Również na komentarze @meuh:

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

Więc można powiedzieć, z wysokim stopniem pewności truei falsepliki wykonywalne istnieją głównie na miano z innymi programami .

Odtąd odpowiedź będzie się koncentrować na /bin/truepliku binarnym z coreutilspakietu w bitach 9/64 Debiana. (z /usr/bin/truesystemem RedHat. RedHat i Debian używają obu coreutilspakietów, przeanalizowali skompilowaną wersję tego drugiego, mając go pod ręką).

Jak widać w pliku źródłowym false.c, /bin/falsejest on kompilowany z (prawie) tym samym kodem źródłowym co /bin/true, po prostu zwracając EXIT_FAILURE (1), więc ta odpowiedź może być zastosowana do obu plików binarnych.

#define EXIT_STATUS EXIT_FAILURE
#include "true.c"

Jak może to również potwierdzić oba pliki wykonywalne o tym samym rozmiarze:

$ 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

Niestety, bezpośrednie pytanie do odpowiedzi why are true and false so large?może brzmieć, ponieważ nie ma już tak pilnych powodów, aby dbać o ich najlepsze wyniki. Nie są one niezbędne dla bashwydajności, nie są już używane przez bash(skrypty).

Podobne uwagi dotyczą ich wielkości, 26 KB dla sprzętu, który mamy obecnie, jest nieznaczna. Przestrzeń nie jest już na wagę złota dla typowego serwera / pulpitu i nawet nie zadają sobie trudu, aby użyć tego samego pliku binarnego dla falsei true, ponieważ jest on tylko dwukrotnie wdrażany w dystrybucji coreutils.

Koncentrując się jednak w prawdziwym duchu pytania, dlaczego coś, co powinno być tak proste i małe, staje się tak duże?

Rzeczywisty rozkład odcinków /bin/truejest taki, jak pokazują te wykresy; główny kod + dane wynosi około 3 KB z pliku binarnego 26 KB, co stanowi 12% wielkości pliku /bin/true.

trueNarzędzie dostaje kod rzeczywiście bardziej cruft biegiem lat, przede wszystkim wsparcie dla standardu --versioni --help.

Jednak nie jest to (jedyne) główne uzasadnienie tego, że jest tak duży, ale raczej, że jest dynamicznie łączony (za pomocą współdzielonych bibliotek), ale ma także część ogólnej biblioteki często używanej przez coreutilspliki binarne połączone jako biblioteka statyczna. Metada do budowy elfpliku wykonywalnego również stanowi znaczną część pliku binarnego, ponieważ jest to stosunkowo niewielki plik, jak na dzisiejsze standardy.

Reszta odpowiedzi ma na celu wyjaśnienie, w jaki sposób zbudowaliśmy następujące wykresy szczegółowo przedstawiające skład /bin/truewykonywalnego pliku binarnego i jak doszliśmy do tego wniosku.

bintrue bintrue2

Jak mówi @Maks, plik binarny został skompilowany z C; zgodnie z moim komentarzem również potwierdzono, że pochodzi on z rdzeni rdzeniowych. Wskazujemy bezpośrednio na autora (ów) git https://github.com/wertarbyte/coreutils/blob/master/src/true.c , zamiast gnu git jako @Maks (te same źródła, różne repozytoria - to repozytorium został wybrany, ponieważ ma pełne źródło coreutilsbibliotek)

Widzimy tutaj różne elementy składowe /bin/truepliku binarnego (Debian 9 - 64 bity od 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

Tych:

  • tekst (zwykle kod) ma około 24 KB
  • dane (zmienne zainicjowane, głównie łańcuchy) mają wielkość około 1 KB
  • bss (niezainicjowane dane) 0,5 KB

Z 24 KB około 1 KB służy do naprawy 58 funkcji zewnętrznych.

To pozostawia około 23 KB na resztę kodu. Pokażemy poniżej, że rzeczywisty kod pliku głównego - main () + use () jest skompilowany około 1 KB, i wyjaśnimy, do czego służą pozostałe 22 KB.

readelf -S truePrzechodząc dalej do pliku binarnego , widzimy, że chociaż plik binarny ma 26159 bajtów, rzeczywisty skompilowany kod to 13017 bajtów, a reszta to asortowany kod danych / inicjalizacji.

Nie true.cjest to jednak cała historia, a 13 KB wydaje się być zbyt wygórowane, gdyby był tylko tym plikiem; możemy zobaczyć wywołane funkcje main(), które nie są wymienione w funkcjach zewnętrznych widocznych u elfa z objdump -T true; funkcje obecne w:

Te dodatkowe funkcje niepowiązane zewnętrznie main()to:

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

Moje pierwsze podejrzenie było częściowo poprawne, podczas gdy biblioteka korzysta z bibliotek dynamicznych, /bin/trueplik binarny jest duży *, ponieważ zawiera pewne biblioteki statyczne * (ale to nie jedyna przyczyna).

Kompilowanie kodu C zwykle nie jest tak nieefektywne, aby nie uwzględniać takiej przestrzeni, stąd moje początkowe podejrzenie, że coś było nie tak.

Dodatkowa przestrzeń, prawie 90% wielkości pliku binarnego, to rzeczywiście dodatkowe metadane bibliotek / elfów.

Podczas używania Hoppera do dezasemblacji / dekompilacji pliku binarnego w celu zrozumienia, gdzie są funkcje, można zobaczyć, że skompilowany kod binarny funkcji true.c / use () ma w rzeczywistości 833 bajty, a funkcji true.c / main () jest 225 bajtów, czyli mniej więcej nieco mniej niż 1 KB. Logika funkcji wersji, która jest zakopana w bibliotekach statycznych, wynosi około 1 KB.

Rzeczywiste skompilowane main () + użycie () + wersja () + ciągi znaków + zmienne zużywają tylko około 3 KB do 3,5 KB.

To jest rzeczywiście ironiczne, takie małe i skromne narzędzia stały się większe z powodów wyjaśnionych powyżej.

powiązane pytanie: Zrozumienie, co robi plik binarny systemu Linux

true.c main () z wywoływanymi funkcjami:

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

Rozmiar dziesiętny różnych sekcji pliku binarnego:

$ 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

Wyjście z 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)

Wyjście objdump -T true(funkcje zewnętrzne dynamicznie połączone w czasie wykonywania)

$ 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

5
Po ostatnim programowaniu z mikrokontrolerem 64kB + 2kB, 28kB nie wydaje się aż tak małe ...
Barleyman 26.01.2018

1
@Barleyman masz OpenWRT, yocto, uClinux, uclib, busybox, microcoreutils i inne rozwiązania dla tego rodzaju środowisk. Edytowałem post z twoją troską.
Rui F Ribeiro

4
@Barleyman: Jeśli optymalizowałeś binarny rozmiar pliku wykonywalnego, możesz zaimplementować truelub falseza pomocą 45-bajtowego pliku wykonywalnego ELF x86, pakując kod wykonywalny (4 instrukcje x86) wewnątrz nagłówka programu ELF (bez obsługi żadnych opcji wiersza poleceń!) . Samouczek Whirlwind na temat tworzenia plików wykonywalnych ELF dla systemu Linux . (Lub nieco większy, jeśli chcesz uniknąć, w zależności od szczegółów implementacji modułu ładującego ELF dla systemu Linux: P)
Peter Cordes

3
Nie, naprawdę nie. Na przykład Yocto można wcisnąć w mniej niż megabajt, który jest stertą i przekracza ponad 64kB. W tego rodzaju urządzeniu możesz używać pewnego rodzaju RTOS z podstawowym zarządzaniem procesami / pamięcią, ale nawet te mogą łatwo stać się zbyt ciężkie. Napisałem prosty współpracujący system wielowątkowy i wykorzystałem wbudowaną ochronę pamięci, aby zabezpieczyć kod przed nadpisaniem. Wszystko wskazuje na to, że oprogramowanie wewnętrzne zużywa obecnie około 55 kB, więc nie ma zbyt wiele miejsca na dodatkowe koszty ogólne. Te gigantyczne stoły do ​​wyszukiwania 2kB ..
Barleyman

2
@PeterCordes na pewno, ale potrzebujesz kilku wielkości więcej zasobów, zanim Linux stanie się rentowny. Co do wartości, C ++ też tak naprawdę nie działa w tym środowisku. W każdym razie nie standardowe biblioteki. Iostream ma już około
200kB

34

Implementacja prawdopodobnie pochodzi z rdzeni GNU. Te pliki binarne są kompilowane z C; nie podjęto żadnych szczególnych starań, aby były one mniejsze niż są domyślnie.

Możesz spróbować skompilować trywialną implementację truesiebie, a zauważysz, że ma już kilka KB. Na przykład w moim systemie:

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

Oczywiście twoje pliki binarne są jeszcze większe. Jest tak, ponieważ obsługują one również argumenty wiersza poleceń. Spróbuj uruchomić /usr/bin/true --helplub /usr/bin/true --version.

Oprócz danych ciągowych, plik binarny zawiera logikę do analizowania flag wiersza poleceń itp. To najwyraźniej stanowi około 20 KB kodu.

W celach informacyjnych kod źródłowy można znaleźć tutaj: http://git.savannah.gnu.org/cgit/coreutils.git/tree/src/true.c


2
Do Twojej wiadomości narzekałem na te implementacje coreutils na ich narzędziu do śledzenia błędów, ale nie ma szans, aby to naprawić lists.gnu.org/archive/html/bug-coreutils/2016-03/msg00040.html
rudimeier

7
To nie jest logika argumentów, C nie jest tak nieefektywne ... jest wbudowanymi bibliotekami / zadaniami utrzymania domu. Zobacz moją odpowiedź na krwawe szczegóły.
Rui F Ribeiro

8
Jest to mylące, ponieważ sugeruje, że skompilowany kod maszynowy (z C lub w inny sposób) zajmuje tyle miejsca - narzut na rzeczywisty rozmiar ma więcej wspólnego z ogromnymi ilościami standardowej biblioteki C / płyty uruchomieniowej, którą kompiluje kompilator w aby współpracować z biblioteką C (glibc, chyba że słyszałeś, że twój system prawdopodobnie używa czegoś innego) oraz, w mniejszym stopniu, nagłówki / metadane ELF (z których wiele nie jest absolutnie konieczne, ale uważane za wystarczające w domyślnych kompilacjach).
mtraceur

2
Rzeczywiste ciągi main () + wykorzystanie () + w obu funkcjach wynoszą około 2 KB, a nie 20 KB.
Rui F Ribeiro

2
@JdeBP logika dla --version / version funtions 1KB, --usage / - help 833 bajtów, main () 225 bajtów, a całe dane statyczne pliku binarnego to 1KB
Rui F Ribeiro

25

Zredukowanie ich do podstawowej funkcjonalności i pisanie w asemblerze daje znacznie mniejsze pliki binarne.

Oryginalne pliki binarne prawda / fałsz zapisywane są w języku C, który ze swej natury ściąga różne biblioteki i odniesienia do symboli. Jeśli uruchomisz, readelf -a /bin/truejest to dość zauważalne.

352 bajty dla odizolowanego pliku wykonywalnego ELF (z miejscem na zaoszczędzenie kilku bajtów poprzez optymalizację asm dla rozmiaru kodu).

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

Lub, stosując nieco nieprzyjemne / pomysłowe podejście (od uznania do stalkr ), utwórz własne nagłówki ELF, zmniejszając je do 132 127 bajtów. Wjeżdżamy na terytorium 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
$

2
Komentarze nie są przeznaczone do rozszerzonej dyskusji; ta rozmowa została przeniesiona do czatu .
terdon

2
Zobacz także ten doskonały napis
mic_e

3
Używasz int 0x8032-bitowego ABI w 64-bitowym pliku wykonywalnym, co jest niezwykłe, ale obsługiwane . Używanie syscallniczego by cię nie uratowało. Wysokie bajty ebxsą ignorowane, więc możesz użyć 2 bajtów mov bl,1. Lub oczywiście xor ebx,ebxzero . Linux inits rejestrów całkowitych na zero, więc mógł tylko inc eaxdostać 1 = __NR_exit i386 (ABI).
Peter Cordes

1
Zaktualizowałem kod na twoim golfowym przykładzie, aby użyć 64-bitowego ABI, i zmniejszyłem go do 127 bajtów true. (Nie widzę łatwy sposób zarządzać mniej niż 128 bajtów false, choć inne niż przy użyciu 32-bitowego ABI lub korzystając z faktu, że Linux zer rejestrów na starcie procesu, tak mov al,252(2 bajty) pracuje. push imm8/ pop rdiBy również działają zamiast leaustawiania edi=1, ale nadal nie możemy pobić 32-bitowego ABI, w którym moglibyśmy mov bl,1bez prefiksu REX
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

Całkiem duży na moim Ubuntu 16.04. dokładnie ten sam rozmiar? Co czyni je tak dużymi?

strings $(which true)

(fragment:)

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'

Ach, istnieje pomoc dla prawdy i fałszu, więc spróbujmy:

true --help 
true --version
#

Nic. Ach, była inna linia:

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

W moim systemie jest to / bin / true, a nie / 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.

Jest więc pomoc, są informacje o wersji, wiążące bibliotekę w celu internacjonalizacji. To wyjaśnia znaczną część rozmiaru, a powłoka i tak używa swojego zoptymalizowanego polecenia przez większość czasu.


W tym biblioteki statyczne i połowę wielkości pliku binarnego dla elf metada. Zobacz moją odpowiedź.
Rui F Ribeiro
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.