GNU/Linux >> Znalost Linux >  >> Linux

Proč jsou pravdivé a nepravdivé tak velké?

V minulosti /bin/true a /bin/false v shellu byly ve skutečnosti skripty.

Například v 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
$  

V dnešní době alespoň v bash , true a false příkazy jsou implementovány jako vestavěné příkazy shellu. Ve výchozím nastavení se tedy nevyvolávají žádné spustitelné binární soubory, a to jak při použití false a true direktivy v bash příkazového řádku a uvnitř skriptů shellu.

Z bash zdroj, 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
    };

Také podle komentářů @meuh:

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

Takže lze s vysokou mírou jistoty říci true a false spustitelné soubory existují hlavně pro volání z jiných programů .

Od této chvíle se odpověď zaměří na /bin/true binární z coreutils balíček v Debianu 9/64 bitů. (/usr/bin/true běžící RedHat. RedHat i Debian používají coreutils balíček, analyzoval zkompilovanou verzi posledně jmenovaného a měl ji více po ruce).

Jak je vidět ve zdrojovém souboru false.c , /bin/false je zkompilován s (téměř) stejným zdrojovým kódem jako /bin/true , místo toho vrací EXIT_FAILURE (1), takže tuto odpověď lze použít pro oba binární soubory.

#define EXIT_STATUS EXIT_FAILURE
#include "true.c"

Jak to také může být potvrzeno tím, že oba spustitelné soubory mají stejnou velikost:

$ 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

Bohužel, přímá otázka na odpověď why are true and false so large? může být, protože už nejsou tak naléhavé důvody, proč se starat o jejich špičkový výkon. Pro bash nejsou nezbytné výkon, již nepoužívá bash (skriptování).

Podobné komentáře se týkají i jejich velikosti, 26KB je pro dnešní hardware nepodstatné. Prostor už není pro typický server/desktop na prvním místě a už se ani neobtěžují používat stejnou binární hodnotu pro false a true , protože je právě nasazen dvakrát v distribucích pomocí coreutils .

Zaměříme se však ve skutečném duchu na otázku, proč se něco, co by mělo být tak jednoduché a malé, tak velké?

Skutečná distribuce sekcí /bin/true je jak ukazují tyto grafy; hlavní kód + data činí zhruba 3 kB z 26 kB binárního souboru, což představuje 12 % velikosti /bin/true .

true obslužný program získal v průběhu let skutečně více cruft kódu, zejména standardní podporu pro --version a --help .

Není to však (jediné) hlavní odůvodnění pro to, že je tak velký, ale spíše, zatímco je dynamicky propojen (pomocí sdílených knihoven), má také část generické knihovny běžně používané coreutils binární soubory propojené jako statická knihovna. Metadáta pro vytvoření elf spustitelný soubor také tvoří významnou část binárního souboru, na dnešní poměry jde o relativně malý soubor.

Zbytek odpovědi je pro vysvětlení, jak jsme se dostali k vytvoření následujících grafů s podrobným složením /bin/true spustitelný binární soubor a jak jsme k tomuto závěru dospěli.

Jak říká @Maks, binární soubor byl zkompilován z C; podle mého komentáře je také potvrzeno, že pochází z coreutils. Ukazujeme přímo na git autora (autorů) https://github.com/wertarbyte/coreutils/blob/master/src/true.c, místo na gnu git jako @Maks (stejné zdroje, různé repozitáře - toto úložiště byl vybrán, protože má úplný zdroj coreutils knihovny)

Můžeme vidět různé stavební bloky /bin/true binární zde (Debian 9 - 64 bitů z 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

Z těch:

  • text (obvykle kód) má přibližně 24 kB
  • data (inicializované proměnné, většinou řetězce) mají přibližně 1 kB
  • bss (neinicializovaná data) 0,5 kB

Z 24 kB je asi 1 kB na opravu 58 externích funkcí.

To stále zbývá zhruba 23 kB pro zbytek kódu. Níže si ukážeme, že skutečný hlavní soubor - kód main()+usage() má zkompilovaný přibližně 1 kB, a vysvětlíme, k čemu se používá zbývajících 22 kB.

Posouvání dále v binárním kódu pomocí readelf -S true , můžeme vidět, že zatímco binární kód má 26 159 bajtů, skutečný zkompilovaný kód má 13 017 bajtů a zbytek jsou různá data/inicializační kód.

Nicméně true.c není celý příběh a 13 kB se zdá být dost přehnaných, pokud by šlo pouze o tento soubor; můžeme vidět funkce volané v main() které nejsou uvedeny v externích funkcích viděných u elfa s objdump -T true; funkce, které jsou přítomné na:

  • 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

Tyto dodatečné funkce nejsou externě propojeny v main() jsou:

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

Takže moje první podezření bylo částečně správné, zatímco knihovna používá dynamické knihovny, /bin/true binární je velký *protože má nějaké statické knihovny, které jsou součástí* (ale to není jediná příčina).

Kompilace kódu C obvykle není tak neefektivní na to, aby byl takový prostor nezodpovězený, proto jsem původně tušil, že něco není v pořádku.

Prostor navíc, téměř 90 % velikosti binárního souboru, jsou skutečně extra metadata knihoven/elfů.

Při použití Hopperu k rozebrání/dekompilaci binárního kódu k pochopení, kde jsou funkce, je vidět, že zkompilovaný binární kód funkce true.c/usage() má ve skutečnosti 833 bajtů a funkce true.c/main() je 225. bajtů, což je zhruba o něco méně než 1 kB. Logika funkcí verzí, která je pohřbena ve statických knihovnách, je kolem 1 kB.

Skutečně zkompilované main()+usage()+version()+strings+vars zabírají pouze přibližně 3 kB až 3,5 kB.

Je skutečně ironické, že takové malé a skromné ​​nástroje se z výše vysvětlených důvodů zvětšily.

související otázka:Pochopení toho, co dělá linuxový binární soubor

true.c main() s problematickým voláním funkce:

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

Desetinná velikost různých částí binárního souboru:

$ 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

Výstup 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)

Výstup objdump -T true (externí funkce dynamicky propojené za běhu)

$ 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

Implementace pravděpodobně pochází z GNU coreutils. Tyto binární soubory jsou kompilovány z C; nebylo vynaloženo žádné zvláštní úsilí, aby byly menší, než jsou ve výchozím nastavení.

Můžete zkusit zkompilovat triviální implementaci true a všimnete si, že už má velikost několik KB. Například v mém systému:

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

Vaše dvojhvězdy jsou samozřejmě ještě větší. To proto, že také podporují argumenty příkazového řádku. Zkuste spustit /usr/bin/true --help nebo /usr/bin/true --version .

Kromě řetězcových dat obsahuje binární soubor logiku pro analýzu příznaků příkazového řádku atd. To zjevně dává asi 20 KB kódu.

Zdrojový kód můžete najít zde:http://git.savannah.gnu.org/cgit/coreutils.git/tree/src/true.c


Jejich odstranění na základní funkčnost a zápis v assembleru vede k mnohem menším binárním souborům.

Původní pravdivé/nepravdivé binární soubory jsou napsány v jazyce C, který ze své podstaty stahuje různé odkazy na knihovny + symboly. Pokud spustíte readelf -a /bin/true to je docela patrné.

352 bajtů pro oříznutý statický spustitelný soubor ELF (s prostorem pro úsporu několika bajtů optimalizací asm pro velikost kódu).

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

Nebo si s trochu ošklivým/důmyslným přístupem (sláva stalkrovi) vytvořte vlastní hlavičky ELF a snižte to na 132 127 bajtů. Zde vstupujeme do oblasti 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
$

Linux
  1. Linux – sloučení /usr/bin A /usr/sbin do /bin (gnu/linux)?

  2. Co je /bin/true?

  3. Nainstalujte binární soubory do /bin, /sbin, /usr/bin a /usr/sbin, interakce s --prefix a DESTDIR

  1. Jaký je rozdíl mezi #!/usr/bin/env bash a #!/usr/bin/bash?

  2. Rozdíl mezi /bin a /usr/bin

  3. Přesunutý obsah /bin do /usr/bin, je možné vrátit zpět?

  1. Rozdíl mezi /bin/false &/sbin/nologin

  2. Bash =~ Regex A Https://regex101.com/?

  3. Jaký je rozdíl mezi /sbin/nologin a /bin/false?