在发现几个常见命令(例如read)实际上是Bash内置文件之后(当在提示符下运行它们时,我实际上正在运行一个两行的shell脚本,该脚本仅转发到该内置文件),我正在查看是否truefalse也是如此。

好吧,它们绝对是二进制文件。

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,而falseexit 1

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


但是我惊讶地发现两个文件都结束了大小为28KB。

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

评论

您应该使用命令-V true而不是。它将输出:true是bash内置的shell。

true和false在每个现代外壳程序中都是内置的,但是系统也包含它们的外部程序版本,因为它是标准系统的一部分,因此直接调用命令(绕过外壳程序)的程序可以使用它们。该命令会忽略内置命令,而仅查找外部命令,这就是为什么它只向您显示外部命令的原因。尝试键入-a true,然后键入-a false。

具有讽刺意味的是,您写了一个很长的问题说:“为什么正确和错误分别为29kb?可执行文件中除了返回码之外还有什么?”

unix的某些早期版本只有一个空文件为true,因为那是一个有效的sh程序,它将返回退出代码0。我真的希望我能找到我几年前读过的有关true实用程序历史的文章,从空文件到今天是怪兽,但我能找到的是:trillian.mit.edu/~jc/humor/ATT_Copyright_true.html

必须-最小的false实现:muppetlabs.com/~breadbox/software/tiny/teensy.html

#1 楼

过去,shell中的/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中,将truefalse命令实现为Shell内置命令。因此,在false命令行和内部shell脚本中使用truebash伪指令时,默认情况下不会调用可执行二进制文件。

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


因此可以肯定地说truefalse可执行文件存在主要是因为被其他程序调用。

从现在开始,答案将集中在Debian 9/64位的/bin/true软件包中的coreutils二进制文件上。 (运行RedHat的/usr/bin/true。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


A ,答案可能是直接的问题,因为已经没有紧迫的理由来关心它们的最佳性能。它们对于why are true and false so large?的性能不是必需的,bash(脚本)已不再使用它们。

类似的评论适用于它们的大小,对于我们如今拥有的那种硬件来说,26KB无关紧要。对于典型的服务器/桌面而言,空间已不再是宝贵的,它们甚至不再为bashfalse使用相同的二进制文件而烦恼,因为它只是在使用true的发行版中进行了两次部署。但是,本着问题的实质精神,为什么那些本应如此简单而又小的事物却变得如此之大呢?

coreutils的各个部分的实际分布如下图所示;在26KB二进制文件中,主代码+数据约占3KB,占/bin/true大小的12%。

多年来,/bin/true实用程序确实获得了更多的原始代码,尤其是对true--version的标准支持。

但是,这并不是(如此)大的主要理由,而是在动态链接(使用共享库)的同时,还拥有--help常用的通用库的一部分链接为静态库的二进制文件。用于构建coreutils可执行文件的metada也占二进制文件的很大一部分,按照今天的标准来说,它是一个相对较小的文件。

其余的答案用于解释我们如何构建以下图表,详细说明elf可执行二进制文件的组成以及如何得出该结论。



正如@Maks所说,二进制文件是从C编译的;根据我的评论,也可以确认它来自coreutils。我们直接指向作者git https://github.com/wertarbyte/coreutils/blob/master/src/true.c,而不是像@Maks一样的gnu git(相同的来源,不同的存储库-此存储库已被选中,因为它具有/bin/true库的完整源代码)

我们可以在此处看到coreutils二进制文件的各种构建块(来自/bin/true的Debian 9-64位):

$ 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

其中的:


文本(通常是代码)大约为24KB
数据(初始化变量,主要是字符串)大约为1KB
/> bss(未初始化的数据)0.5KB

24KB中的大约1KB用于固定58个外部功能。

剩下的大约23KB的代码。下面我们将向您展示实际的主文件-main()+ usage()代码已编译约1KB,并解释了其他22KB的用途。

使用coreutils进一步深入二进制文件,我们可以看到,虽然二进制文件为26159字节,但实际的编译代码为13017字节,其余为各种数据/初始化代码。

然而,readelf -S true并不是全部内容,而13KB似乎还很多如果仅仅是那个文件,则过多;我们可以看到在true.c中调用的函数未在带有main()的小精灵中看到的外部函数中列出;出现在以下位置的功能:



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

那些不在objdump -T true中外部链接的额外功能是:


set_program_name()
close_stdout()
version_etc()

因此,我的第一个怀疑是部分正确的,尽管该库正在使用动态库,但是main()二进制文件很大*因为它包含一些静态库*(但这不是唯一的原因)。

编译C代码通常不会使未说明的空间效率低下,因此我最初的怀疑是不对。

额外的空间,几乎是大小的90%二进制确实是额外的库/精灵元数据。

虽然使用Hopper来对二进制进行反汇编/反编译以了解函数在哪里,但是可以看到true.c / usage()的已编译二进制代码。函数实际上是833个字节,而true.c / main()函数是225个字节,大约略小于1KB。嵌入在静态库中的版本函数逻辑约为1KB。

实际编译的main()+ usage()+ version()+ strings + vars大约只用了3KB到3.5KB。

确实具有讽刺意味的是,如此小巧的实用程序在大小的原因如上所述。 >
 /bin/true 


二进制文件各个部分的十进制大小:

$ 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


true.c的输出

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


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); } 的输出(在运行时动态链接的外部函数)

$ 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

>

评论


最近用64kB + 2kB微控制器进行了一些编程,而28kB似乎还不算小。

–大麦人
18年1月26日在16:49

@Barleyman,您有针对此类环境的OpenWRT,yocto,uClinux,uclib,busybox,microcoreutils和其他解决方案。与您有关的帖子已编辑。

– Rui F Ribeiro
18年1月27日在9:09

@Barleyman:如果您正在优化二进制可执行文件的大小,则可以使用45字节的x86 ELF可执行文件实现true或false,将可执行代码(4个x86指令)打包在ELF程序标头中(不支持任何命令行选项) !)。有关为Linux创建真正的ELF可执行文件的旋风教程。 (或者,如果要避免依赖于Linux ELF loader实施细节,则稍大一些:P)

– Peter Cordes
18年1月28日在23:39

不是,不是例如,Yocto可以塞入小于64kB的堆中,小于一兆字节。在这种设备中,您可能会使用某种具有基本过程/内存管理的RTOS,但即使是RTOS也很容易变得太重。我编写了一个简单的协作多线程系统,并使用内置的内存保护功能来防止代码被覆盖。总而言之,固件现在消耗约55kB,因此那里没有太多空间用于额外开销。那些巨大的2kB查找表..

–大麦人
18年1月29日,0:09

@PeterCordes当然可以,但是在Linux变得可行之前,您需要几个数量级的更多资源。就其价值而言,C ++在该环境中也无法正常工作。好吧,反正不是标准库。 Iostream刚好在200kB等附近。

–大麦人
18年1月29日,0:19

#2 楼

该实现可能来自GNU coreutils。这些二进制文件是从C编译的;无需进行任何特别的操作即可使它们小于默认值。

您可以尝试自己编译true的简单实现,您会发现它的大小已经只有几个KB。例如,在我的系统上:

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


当然,您的二进制文件更大。那是因为它们还支持命令行参数。尝试运行/usr/bin/true --help/usr/bin/true --version

除了字符串数据之外,二进制文件还包括解析命令行标志等的逻辑。显然,这总共增加了大约20 KB的代码。

作为参考,您可以在这里找到源代码:http://git.savannah.gnu.org/cgit/coreutils.git/tree/src/true.c

评论


仅供参考,我一直在抱怨他们的bug跟踪器上的这些coreutils实现,但没有机会得到固定的list.gnu.org/archive/html/bug-coreutils/2016-03/msg00040.html

– rudimeier
18年1月25日在21:43

这不是参数的逻辑,C不是效率低下的...是内联库/内务处理任务。看一下我对血腥细节的回答。

– Rui F Ribeiro
18年1月25日在21:59



这具有误导性,因为它表明编译的机器代码(来自C还是其他方式)占用了大量空间-实际的大小开销与由编译器内联的大量标准C库/运行时样板有关。以便与C库(glibc,除非您听说过系统可能使用其他东西)进行互操作,并在较小程度上与ELF标头/元数据(其中很多并不是严格必要的,但被认为足够有价值)进行互操作包括在默认版本中)。

–mtraceur
18年1月25日在22:10

这两个函数的实际main()+ usage()+ strings大约为2KB,而不是20KB。

– Rui F Ribeiro
18年1月25日在23:11



用于--version / version函数的@JdeBP逻辑1KB,-usage /-help 833字节,main()225字节,二进制文件的整个静态数据为1KB

– Rui F Ribeiro
18年1月26日在9:04



#3 楼

将它们分解为核心功能并用汇编器进行写操作可产生更小的二进制文件。

原始的正确/错误二进制文件是用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标头,将其减小到132127字节。我们正在此处输入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
$


评论


评论不作进一步讨论;此对话已移至聊天。

– terdon♦
18年1月28日在16:27

另请参阅以下出色的文章:muppetlabs.com/~breadbox/software/tiny/teensy.html

–mic_e
18年1月28日在21:38

您在64位可执行文件中使用int 0x80 32位ABI,这很常见,但受支持。使用syscall不会为您节省任何费用。 ebx的高字节被忽略,因此您可以使用2字节mov bl,1。或者当然xor ebx,ebx为零。 Linux的整数寄存器为零,因此您可以增加eax以获得1 = __NR_exit(i386 ABI)。

– Peter Cordes
18年1月28日在23:48

我更新了您打高尔夫球的示例中的代码,以使用64位ABI,并将其打高尔夫球到127个字节为true。 (不过,除了使用32位ABI或利用Linux零在进程启动时注册的事实外,我看不到一种管理少于128个字节的错误的简便方法,因此,动态252(2个字节)。push imm8 / pop rdi也可以代替lea来设置edi = 1,但是我们仍然无法击败没有REX前缀的bl,1的32位ABI。

– Peter Cordes
18年1月29日,0:17



#4 楼

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.


所以这里有帮助,这里有版本信息,绑定到一个库进行国际化。这解释了很多大小,并且外壳无论如何在大多数时间都使用其优化的命令。

评论


包括静态库,以及elf metada二进制文件大小的一半。看我的答案。

– Rui F Ribeiro
18-2-18在19:13



#5 楼

Rui F Ribeiro接受的答案提供了很多很好的信息,但是却缺少了一些信息,我觉得它给读者留下了一种误导性的印象,即代码大小相对于“ ELF开销”之类的东西很小。


因此,我的第一个怀疑是部分正确的,尽管该库正在使用动态库,但是/bin/true二进制文件很大*因为它包含一些静态库*(但这不是唯一原因)。


静态链接是基于对象文件的粒度(或者使用--gc-sections更好),因此谈论静态库“链接”是没有意义的;链接的唯一部分是所使用的内容,这里的代码大小是true的coreutils版本实际(免费)使用的代码。将它与true.c中出现的内容分开计算是没有意义的。


额外的空间几乎是二进制文件大小的90%,实际上是额外的库/ elf元数据。


ELF元数据几乎完全是动态链接所必需的表,远不及大小的90%。以下是来自size -A输出的与之相关的行:


section               size      addr
.interp                 28       568
.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
.plt                   752      4800
.plt.got                 8      5552
.dynamic               480   2125272
.got                    48   2125752
.got.plt               392   2125824



总共约5.5k,或平均约100k每个动态符号的字节数(不公平,因为有些不是基于每个符号的,但这是一个有意义的数字)。


.eh_frame_hdr          572     21728
.eh_frame             2908     22304



这3.5k是DWARF展开表,用于C ++异常处理,自省回溯等。它们对于true完全没有用,但在所有输出中都包含在GCC策略中,除非您使用非常冗长的选项-fno-asynchronous-unwind-tables明确省略了它们。背后的动机在我对Stack Overflow的回答中得到了解释。

所以我将最终的故障描述为:


程序代码的16k并阅读只(可共享)数据
5.5k的动态链接胶
3.5k的展开表
<1k misc

特别值得注意的是,如果动态链接库所需的代码量足够少,则静态链接可能小于动态链接。