Почему правда и ложь так велики?


80

Узнав, что некоторые общие команды (такие как read) на самом деле являются встроенными в Bash (и когда я запускаю их в командной строке, я на самом деле запускаю двухстрочный сценарий оболочки, который просто перенаправляет их во встроенную систему), я посмотрел, нет ли верно для trueи false.

Ну, они, безусловно, двоичные файлы.

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$

Однако больше всего меня удивило их размер. Я ожидал, что они будут всего несколько байтов каждый, как trueэто в основном справедливо exit 0и falseесть exit 1.

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

Однако, к моему удивлению, оба файла имеют размер более 28 КБ.

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$

Итак, мой вопрос: почему они такие большие? Что в исполняемом файле кроме кода возврата?

PS: я использую RHEL 7.4


9
Вы должны использовать command -V trueне which. Будет выводиться: true is a shell builtinдля bash.
meuh

32
trueи false являются встроенные функции в каждой современной оболочке, но эти системы также включает в себя внешние программные версии из них , потому что это часть стандартной системы , так что программы вызова команд напрямую ( в обход оболочки) может использовать их. whichигнорирует встроенные функции и ищет только внешние команды, поэтому он показывает только внешние команды. Попробуй type -a trueи type -a falseвзамен.
mtraceur

74
Это парадоксально , что вы пишете такой длинный вопрос , чтобы сказать : «Почему trueи false29KB каждый? Что в исполняемый файл, отличный от кода возврата?»
Дэвид Ричерби

7
В некоторых ранних версиях Unix просто был пустой файл для true, так как это была действительная программа sh, которая возвращала бы код выхода 0. Я действительно хотел бы найти статью, которую я читал много лет назад об истории истинной утилиты, из пустого файла в это чудовище сегодня, но все, что я смог найти, это: trillian.mit.edu/~jc/humor/ATT_Copyright_true.html
Филипп

9
Обязательный - наименьшая реализация false: muppetlabs.com/~breadbox/software/tiny/teensy.html
d33tah

Ответы:


117

В прошлом /bin/trueи /bin/falseв оболочке были собственно скрипты.

Например, в 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
$  

В настоящее время, по крайней мере bash, в trueи falseкоманды реализованы в виде встроенных команд оболочки. Таким образом , не исполняемые бинарные файлы не вызывается по умолчанию, как при использовании falseи trueдиректив в bashкомандной строке и в сценариях оболочки.

Из bashисточника 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
    };

Также за @meuh комментарии:

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

Так что можно сказать с высокой степенью уверенности trueи falseисполняемые файлы существуют в основном для того вызваны из других программ .

Отныне ответ будет сосредоточен на /bin/trueдвоичном файле из coreutilsпакета в Debian 9/64 бит. (при /usr/bin/trueзапуске RedHat. RedHat и Debian используют оба coreutilsпакета, проанализировали скомпилированную версию последнего, имеющую его под рукой).

Как видно из исходного файла false.c, /bin/falseон компилируется с (почти) тем же исходным кодом, что /bin/trueи просто возвращает EXIT_FAILURE (1), поэтому этот ответ может быть применен к обоим двоичным файлам.

#define EXIT_STATUS EXIT_FAILURE
#include "true.c"

Как это также может быть подтверждено обоими исполняемыми файлами одинакового размера:

$ 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

Увы, прямой вопрос к ответу why are true and false so large?может быть, потому что нет больше неотложных причин заботиться об их максимальной производительности. Они не важны для bashпроизводительности и больше не используются bash(скриптами).

Аналогичные комментарии относятся к их размеру, 26 КБ для того оборудования, которое у нас есть в настоящее время, незначительно. Пространство не в почете для типичного сервера / рабочего стола больше, и они даже не потрудились больше использовать тот же двоичный файл для falseи true, так как он просто развернут дважды в распределениях с использованием coreutils.

Сосредоточение, однако, в реальном духе вопроса, почему то, что должно быть таким простым и маленьким, становится таким большим?

Реальное распределение разделов таково, /bin/trueкак показывают эти диаграммы; основной код + данные составляют примерно 3 КБ из двоичного файла размером 26 КБ, что составляет 12% от размера /bin/true.

За эти годы trueутилита действительно получила больше грязного кода, прежде всего стандартную поддержку --versionи --help.

Тем не менее, это не единственное (главное) обоснование того, что он такой большой, а скорее потому, что он динамически связан (использует разделяемые библиотеки), а также имеет часть универсальной библиотеки, обычно используемой coreutilsдвоичными файлами, связанными как статическая библиотека. Метада для построения elfисполняемого файла также составляет значительную часть двоичного файла, являющегося относительно небольшим файлом по современным стандартам.

Оставшаяся часть ответа - для объяснения того, как нам удалось построить следующие диаграммы, подробно описывающие состав /bin/trueисполняемого двоичного файла и как мы пришли к такому выводу.

bintrue bintrue2

Как говорит @Maks, двоичный файл был скомпилирован из C; Согласно моему комментарию, также подтверждается, что это от coreutils. Мы указываем непосредственно на автора (ов) git https://github.com/wertarbyte/coreutils/blob/master/src/true.c вместо gnu git as @Maks (те же источники, разные репозитории - этот репозиторий был выбран, так как имеет полный источник coreutilsбиблиотек)

Мы можем увидеть различные строительные блоки /bin/trueдвоичного файла здесь (Debian 9 - 64 бита из 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

Из тех:

  • текст (обычно код) составляет около 24 КБ
  • данные (инициализированные переменные, в основном строки) составляют около 1 КБ
  • bss (неинициализированные данные) 0.5KB

Из 24 КБ около 1 КБ предназначено для исправления 58 внешних функций.

Это все еще оставляет около 23 КБ для остальной части кода. Ниже мы покажем, что фактический код main file - main () + using () составляет около 1 КБ, и объясним, для чего используются другие 22 КБ.

Детализируя далее двоичный файл с помощью readelf -S true, мы можем видеть, что, хотя двоичный файл равен 26159 байтам, фактический скомпилированный код составляет 13017 байт, а остальное представляет собой отсортированный код данных / инициализации.

Тем true.cне менее, это еще не все, и 13KB кажется чрезмерным, если бы это был только этот файл; мы можем видеть функции, вызываемые в main(), которые не перечислены во внешних функциях, видимых в эльфе с objdump -T true; функции, которые присутствуют в:

Те дополнительные функции, которые не связаны внешне в main():

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

Поэтому мое первое подозрение было отчасти верным, хотя библиотека использует динамические библиотеки, /bin/trueдвоичный файл большой *, потому что в него включены некоторые статические библиотеки * (но это не единственная причина).

Компиляция кода C не обычно , что неэффективно , имеющие такое пространство неучтенных, следовательно , мое первоначальное подозрение , что что - то неладно.

Дополнительное пространство, почти 90% размера двоичного файла, действительно является дополнительными метаданными библиотек / эльфов.

Используя Hopper для дизассемблирования / декомпиляции двоичного файла, чтобы понять, где находятся функции, можно увидеть, что скомпилированный двоичный код функции true.c / creation () на самом деле составляет 833 байта, а функции true.c / main () - 225. байт, что примерно немного меньше 1 КБ. Логика для функций версий, скрытых в статических библиотеках, составляет около 1 КБ.

Фактически скомпилированные main () + using () + version () + strings + vars занимают всего около 3KB до 3,5KB.

Это действительно иронично, такие маленькие и скромные утилиты стали больше в размерах по причинам, изложенным выше.

связанный вопрос: Понимание того, что делает бинарный файл Linux

true.c main () с вызывающими ошибочными вызовами функций:

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

Десятичный размер различных разделов двоичного файла:

$ 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

Выход из 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)

Вывод objdump -T true(внешние функции, динамически связанные во время выполнения)

$ 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
После недавнего программирования с использованием микроконтроллера 64 КБ + 2 КБ, 28 КБ не кажутся такими уж маленькими ..
Barleyman

1
@ Barleyman у вас есть OpenWRT, yocto, uClinux, uclib, busybox, microcoreutils и другие решения для подобных сред. Отредактировал пост с вашей заботой.
Руи Ф Рибейро

4
@Barleyman: Если вы оптимизации для бинарного исполняемого размера, можно реализовать trueили falseс 45-байтовой x86 ELF исполняемый файл, паковать исполняемый код (4 инструкции x86) внутри (! Без поддержки каких - либо параметров командной строки) заголовка ELF программы , Вихревое руководство по созданию действительно исполняемых исполняемых файлов ELF для Linux . (Или немного больше, если вы хотите избежать зависимости от деталей реализации загрузчика Linux ELF: P)
Питер Кордес

3
Не на самом деле нет. Например, Yocto может быть вмещен менее чем в мегабайт, что составляет кучу и более 64 КБ. В этом типе устройства вы можете использовать какую-либо ОСРВ с элементарным управлением процессом / памятью, но даже они могут легко стать слишком тяжелыми. Я написал простую совместную многопоточную систему и использовал встроенную защиту памяти для защиты кода от перезаписи. В общем, прошивка сейчас потребляет около 55 КБ, так что места для дополнительных накладных расходов не так уж много. Эти огромные 2 КБ таблицы поиска.
Барлимен

2
@PeterCordes, конечно, но вам нужно несколько величин больше ресурсов, прежде чем Linux станет жизнеспособным. Что бы ни стоило, C ++ тоже не работает в этой среде. Ну, не стандартные библиотеки в любом случае. Iostream - около 200kB и т. Д.
Barleyman

34

Реализация, вероятно, исходит от GNU coreutils. Эти двоичные файлы составлены из C; не было предпринято никаких особых усилий, чтобы сделать их меньше, чем они есть по умолчанию.

Вы можете попробовать скомпилировать тривиальную реализацию trueсамостоятельно, и вы заметите, что ее размер уже несколько КБ. Например, в моей системе:

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

Конечно, ваши двоичные файлы еще больше. Это потому, что они также поддерживают аргументы командной строки. Попробуйте запустить /usr/bin/true --helpили /usr/bin/true --version.

В дополнение к строковым данным, двоичный файл содержит логику для разбора флагов командной строки и т. Д. Это, очевидно, составляет около 20 КБ кода.

Для справки вы можете найти исходный код здесь: http://git.savannah.gnu.org/cgit/coreutils.git/tree/src/true.c


2
К вашему сведению, я жаловался на эти реализации coreutils на их баг-трекере, но у меня
rudimeier

7
Это не логика для аргументов, C не настолько неэффективен ... это встроенные библиотеки / домашние задачи. Посмотрите на мой ответ для кровавых деталей.
Руи Ф Рибейро

8
Это вводит в заблуждение, поскольку предполагает, что скомпилированный машинный код (из C или иным образом) занимает огромное количество места - фактические накладные расходы больше связаны с огромным количеством стандартной библиотеки C / шаблона времени выполнения, которое компилируется компилятором в для взаимодействия с библиотекой C (glibc, если вы не слышали, что ваша система использует что-то еще, вероятно), и, в меньшей степени, заголовки / метаданные ELF (многие из которых не являются строго необходимыми, но считаются достаточно стоящими) включить в сборки по умолчанию).
mtraceur

2
Фактические строки main () + using () + в обеих функциях составляют около 2 КБ, а не 20 КБ.
Руи Ф. Рибейро

2
Логика @JdeBP для функций --version / version 1KB, --usage / - help 833 байта, main () 225 байтов, а все статические данные двоичного файла
Rui F Ribeiro

25

Разъединение их до основной функциональности и запись на ассемблере дает намного меньшие двоичные файлы.

Исходные двоичные файлы true / false написаны на C, который по своей природе тянет в различные библиотеки + ссылки на символы. Если вы запускаете readelf -a /bin/trueэто довольно заметно.

352 байта для удаленного статического исполняемого файла ELF (с возможностью сэкономить пару байтов путем оптимизации asm для размера кода).

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

Или, с помощью немного неприятного / изобретательного подхода ( слава к сталкеру ), создайте свои собственные заголовки ELF, уменьшив его до 132 127 байт. Мы входим на территорию 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
Комментарии не для расширенного обсуждения; этот разговор был перенесен в чат .
Terdon

2
Также посмотрите эту превосходную рецензию
mic_e

3
Вы используете int 0x8032-битный ABI в 64-битном исполняемом файле, что необычно, но поддерживается . Использование syscallничего не спасет. Старшие байты ebxигнорируются, поэтому вы можете использовать 2 байта mov bl,1. Или конечно xor ebx,ebxза ноль . В Linux целочисленные регистры записываются в ноль, так что вы можете просто inc eaxполучить 1 = __NR_exit (i386 ABI).
Питер Кордес

1
Я обновил код вашего примера игры в гольф, чтобы использовать 64-битный ABI, и увеличил его до 127 байт true. (Однако я не вижу простого способа управления менее чем 128 байтами false, кроме использования 32-битного ABI или использования того факта, что нули Linux регистрируются при запуске процесса, поэтому mov al,252(2 байта) работает. push imm8/ pop rdiБудет также работают вместо leaнастройки edi=1, но мы все еще не можем превзойти 32-битный ABI там, где мы могли бы mov bl,1без префикса REX
Питер Кордес

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

Довольно большой на моем Ubuntu 16.04 тоже. точно такой же размер? Что делает их такими большими?

strings $(which true)

(Отрывок :)

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'

Ах, есть помощь для истинных и ложных, так что давайте попробуем:

true --help 
true --version
#

Ничего. Ах, была эта другая строка:

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

Так что в моей системе это / bin / true, а не / 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.

Так что есть помощь, есть информация о версии, привязка к библиотеке для интернационализации. Это объясняет большую часть размера, и оболочка использует свою оптимизированную команду в любом случае и большую часть времени.


Включая статические библиотеки и половину размера двоичного файла для метад эльфа. Смотри мой ответ.
Руи Ф. Рибейро
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.