为什么真与假如此之大?

2020-08-27 02:37:20

在发现几个常见命令(如read)实际上是Bash内置命令(并且在提示符下运行它们时,实际上正在运行一个两行的shell脚本,该脚本只是转发到内置命令)之后,我想看看true和false是否也是同样的情况。(=>。

Sh-4.2$哪个true/usr/bin/truesh-4.2$哪个false/usr/bin/false-4.2$file/usr/bin/true/usr/bin/true:ELF 64位lsb可执行文件,x86-64,版本1(Sysv),动态链接(使用共享库),对于GNU/Linux2.6.32,BuildID[sha1]=2697339d3c1923506e10af65aa3120b12295277e,strippedsh-4.2$file/usr/bin/false/false:ELF 64位lsb可执行文件,xsb。对于GNU/Linux2.6.32,BuildID[sha1]=b160fa513fcc13537d7293f05e40444fe5843640,strippedsh-4.2$。

然而,我发现最令我惊讶的是它们的大小。我预计它们每个都只有几个字节,因为TRUE基本上就是出口0,FALSE就是出口1。

Sh-4.2$stat/usr/bin/true文件:';/usr/bin/true';大小:28920个数据块:64个IO数据块:4096个普通文件设备:fd2ch/64812d索引节点:530320个链接:1个访问:(0755/-rwxr-xr-x)uid:(0/根)gid:(0/根)访问:2018年-01-25 19:46:32.703463708+00修改:2016-06-30 09:44:27.000000000+0100更改:2017-12-22:43:17.447563336+0000出生:-sh-。大小:28920个数据块:64个IO数据块:4096个普通文件设备:fd2ch/64812d索引节点:530697个链接:1个访问:(0755/-rwxr-xr-x)uid:(0/根)gid:(0/根)访问:2018年-01-25 20:06:27.210764704+00修改:2016-06-30 09:44:27.000000000+0100更改:2017-12-22:43:18.148561245+0000出生:-sh-。

所以我的问题是:它们为什么这么大?除了返回代码之外,可执行文件中还有什么?

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

TRUE和FALSE是每个现代shell中的内置内容,但是系统也包括它们的外部程序版本,因为它是标准系统的一部分,这样直接调用命令的程序(绕过shell)就可以使用它们。它忽略内置,只查找外部命令,这就是为什么它只显示外部命令的原因。尝试键入-a true,然后键入-a false。 -电子追踪器。

具有讽刺意味的是,你写了这么长的问题,为什么对和错各是29kb?除了返回代码之外,可执行文件中还有什么? -大卫·里切比(David Richerby)

一些早期版本的Unix只有一个空文件为true,因为这是一个返回退出代码0的有效sh程序。我真希望我能找到几年前读到的一篇文章,讲述真正的实用程序从一个空文件到今天的庞然大物的历史,但我能找到的只有以下内容:trillian.mit.edu/~jc/humor/ATT_Copyright_true.html --菲利普。

$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出口1$$cat/bin/true$。

如今,至少在bash中,true和false命令被实现为shell内置命令。因此,在bash命令行和shell脚本内部使用false和true指令时,默认情况下都不会调用可执行二进制文件。

CHAR*POSIX_BUILTINS[]={";别名";,";bg";,";cd";,";命令";,";**false**";,";fc";,";fg";,";getopts";,";作业";,";kill&#。Newgrp";,";pwd";,";读取";,";**true**";,";umask";,";unalias";,";等待";,(char*)null};

因此,可以高度肯定地说,真假可执行文件的存在主要是为了从其他程序调用。

从现在开始,答案将集中在Debian9/64位的coreutilspack中的/bin/true二进制文件上。(/usr/bin/true运行RedHat.。RedHat和Debian都使用coreutils包,分析了后者的编译版本(手头有更多的coreutils包)。

正如可以在源文件false se.c中看到的那样,/bin/false使用(几乎)与/bin/true相同的源代码进行编译,只返回exit_ailure(1),因此这个答案可以应用于两个二进制文件。

$ls-l/bin/true/bin/false-rwxr-xr-x 1根根31464 2017年2月22日/bin/false-rwxr-xr-x 1根根31464 2017年2月22日/bin/true

唉,答案的直接问题是为什么真与假如此之大?有可能,因为再也没有那么紧迫的理由去关心他们的最佳表现了。它们对bash性能不是必需的,bash(脚本)不再使用它们。

类似的评论也适用于它们的大小,26KB对于我们现在拥有的硬件来说微不足道。对于典型的服务器/台式机来说,空间不再是最重要的,他们甚至不再费心为false和true使用相同的二进制文件,因为它只是在使用coreutils的发行版中部署了两次。

然而,按照问题的真正精神,集中注意力,为什么本应如此简单和微小的东西会变得如此大?

如这些图表所示,/bin/true部分的实际分布如下所示;主代码+数据大约为26KB二进制代码中的3KB,相当于/bin/true大小的12%。

多年来,真正的实用程序确实获得了更多粗糙的代码,最引人注目的是对--version和--help的标准支持。

然而,这并不是它如此大的(唯一)主要理由,而是在动态链接(使用共享库)的同时,还将coreutils二进制程序通常使用的泛型库的一部分链接为静态库。用于构建ELF可执行文件的元数据也相当于二进制文件的重要部分,按照今天的标准,它是一个相对较小的文件。

答案的其余部分用于解释我们是如何构建以下图表的,这些图表详细介绍了/bin/true可执行二进制文件的组成,以及我们是如何得出这一结论的。

正如@Maks所说,该二进制文件是从C编译而来的;根据我的评论,也证实它来自coreutils。我们直接指向作者(S)git https://github.com/wertarbyte/coreutils/blob/master/src/true.c,,而不是gnu git,因为@maks(相同的源代码,不同的存储库-选择这个存储库是因为它拥有coreutils库的全部源代码)。

我们可以在这里看到/bin/truebinary的各种构建块(coreutils中的Debian 9-64位):

$FILE/BIN/TRUE/BIN/TRUE:ELF 64位LSB共享对象,x86-64,版本1(SYSV),动态链接,解释器/lib64/ld-linux-x86-64.so.2,对于GNU/LINUX 2.6.32,BuildID[sha1]=9ae82394864538fa7b23b7f87b259ea2a20889c4,剥离$SIZE/BIN/TRUE文本数据bss dec十六进制文件名24583 1160416 26159 662F TRUE。

那么剩余的代码仍然大约只有23KB。我们将在下面显示实际的主文件main()+use()代码大约编译了1KB,并解释其他22KB是用来做什么的。

使用readelf-S true进一步深入二进制文件,我们可以看到,虽然二进制文件是26159字节,但实际编译的代码是13017字节,其余的是分类的数据/初始化代码。

然而,true.c并不是全部内容,如果仅仅是该文件,13KB似乎非常过大;我们可以看到在main()中调用的函数没有在使用objdump-T true的精灵中看到的外部函数中列出;这些函数位于:

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

由于没有考虑到这样的空间,编译C代码的效率通常不会那么低,因此我最初怀疑出了什么问题。

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

虽然使用Hopper反汇编/反编译二进制文件以了解函数的位置,但是可以看到,true.c/use()函数的编译后的二进制代码实际上是833字节,而true.c/main()函数的编译后的二进制代码是225字节,大致略小于1KB。隐藏在静态库中的版本函数的逻辑大约为1KB。

令人啼笑皆非的是,这些小而简陋的公用事业却因上述原因而变得更大。

Intmain(int argc,char**argv){/*仅当--help或--version是唯一的命令行参数时才能识别。*/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(。IF(STREQ(argv[1],";--version";))version_etc(stdout,program_name,package_name,version,Authors,<;-(char*)null);}exit(Exit_Status);}。

$size-A-t true:段大小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.get 8 5552.text 13017 5568.get。.fini_array 8 2125168.jcr 8 2125176.data.rel.ro 88 2125184.dynamic 480 2125272.get 48 2125752.go.plt 392 2125824.data 128 2126240.bss 416 2126368.gnu_debuglink 52 0Total 26211。

$readelf-S true有30个节标题,从偏移量0x7368开始:段头:[NR]Name Type Address Offset Size EntSize Flags Link Info Align[0]NULL 0000000000000000 0000000000000000 00000000000000 0 0 0[1].interp PROGBITS 00000000000238 000000000000001c 0000000000000000 A 0 1[2].note.abi-tag note 00000000000254 00000254 00000000000000000020.interp PROGBITS 00000000000238 0000000000001c 0000000000000000 A 0 0 1[2].note.abi-tag note 00000000000254 00000254 000000000000000020。1 8[6].dynstr STRTAB 0000000000000860 00000860 00000000000002a4 0000000000000000 A 0 0 1[7].gnu.version VERSYM 0000000000000b04 00000b04 000000000076 00000000000002 A 5 0 2[8].gnu.version_r VERNEED 00000000000b80 00000b80 0000000000000060 0000000000000000 A6。.plt.get PROGBITS 000000000015b0 000015b0 0000000000000008 0000000000000000 AX 0 0 8[14].text PROGBITS 000000000015c0 000000000032d9 00000000000000 AX 0 0 16[15].fini PROGBITS 0000000000489c 0000000000000009 0000000000000000 AX。_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 00000000000000 0 01[29].shstrtab STRTAB 00000000000000 00007254 0000000000010f 00000000000000 0 01 Key to Flags:W(写入),A(分配)、X(执行)、M(合并)、S(字符串)、I(信息)、L(链接顺序)、O(需要额外的操作系统处理)、G(组)、T(TLS)、C(压缩)、x(未知)、o(操作系统特定)、E(排除)、l(大)、p(处理器特定)。

$objdump-T truetrue:文件格式elf64-x86-64DYNAMIC符号表:0000000000000000 df*und*0000000000000000 glbc_2.2.5__uflow00000000000000 df*und*00000000000000 glibc_2.2.5 getenv00000000000000 df*und*0000000000000000 glibc_2.2.5 fre0000000000000000

@Barley man:如果您正在优化二进制可执行文件的大小,您可以使用45字节的x86 ELF可执行文件实现true或false,将可执行代码(4个x86指令)打包到ELF程序头中(不支持任何命令行选项!)。这是一个旋风式的教程,介绍如何为Linux创建非常小的ELF可执行文件。(如果希望避免依赖于Linux ELF加载器实现细节,请稍大一些:p) -彼得·科德斯(Peter Cordes)。

不完全是,不是。例如,Yocto可以被塞进不到1兆字节的空间,这是64KB以上的堆积如山的空间。在这类设备中,您可能会使用某种具有基本进程/内存管理的RTOS,但即使是这些RTOS也很容易变得过于繁重。我编写了一个简单的协作多线程系统,并使用内置的内存保护来保护代码不被覆盖。总而言之,固件现在消耗了大约55KB,所以没有太多空间来增加额外的开销。那些巨大的2KB查询表.. -约翰·麦利曼(Barley Man)。

@PeterCordes是肯定的,但在Linux变得可行之前,您还需要几个数量级的更多资源。不管它有什么价值,C++在那个环境中也不能真正工作。嗯,反正不是标准图书馆。IoStream正好在200KB左右,等等。 -约翰·麦利曼(Barley Man)。

实现可能来自GNU coreutils。这些二进制文件是从C编译而来的;没有特别的努力使它们比默认情况下更小。

您可以尝试自己编译True的简单实现,您会注意到它的大小已经很少了。例如,在我的系统上:

$ECHO';int main(){return 0;}';|GCC-xc--o true$wc-c true8136 true。

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

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

仅供参考我一直在抱怨他们的bug跟踪器上的这些核心实现,但是没有机会修复它lists.gnu.org/archive/html/bug-coreutils/2016-03/msg00040.html -鲁迪迈尔(Krudimeier)。

这不是用于论证的逻辑,C语言也不是那么低效…是内联库/内务管理任务。看看我对血淋淋的细节的回答。 --路易·鲁伊·F·里贝罗。

这具有误导性,因为它表明编译后的机器码(来自C或其他语言)占用了大量空间-实际大小开销更多地与大量标准C库/运行时样板有关,编译器为了与C库进行互操作而内联了这些样板(除非您听说您的系统使用了其他东西,否则很可能是glibc),在较小程度上还包括ELF头/元数据(其中很多不是严格需要的,但被认为足够值得包含在默认构建中 -电子追踪器。

这两个函数的实际main()+use()+字符串大约是2KB,而不是20KB。 --路易·鲁伊·F·里贝罗。

@JdeBP逻辑--version/version函数1KB,--用法/--HELP 833字节,main()225字节,整个二进制静态数据为1KB --路易·鲁伊·F·里贝罗。

原始的真/假二进制文件是用C编写的,其本质是引入各种库+符号引用。如果您运行readelf-a。

.