在发现几个常用命令(read
如对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$
然而,令我惊讶的是,这两个文件的大小都超过了 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
过去,
/bin/true
在/bin/false
shell 中实际上是脚本。例如,在 PDP/11 Unix System 7 中:
如今,至少在 中
bash
,true
和false
命令被实现为 shell 内置命令。因此,在命令行和 shell 脚本中使用false
andtrue
指令时,默认情况下不会调用可执行二进制文件。bash
从
bash
源头来看,builtins/mkbuiltins.c
:同样根据@meuh 评论:
所以可以高度肯定地说
true
,false
可执行文件的存在主要是为了被其他程序调用。从现在开始,答案将集中在 Debian 9 / 64 位软件包中的
/bin/true
二进制文件上。coreutils
(/usr/bin/true
运行 RedHat。RedHat 和 Debian 都使用了该coreutils
软件包,分析了后者的编译版本,更容易掌握)。正如在源文件中可以看到的
false.c
,/bin/false
使用(几乎)相同的源代码编译/bin/true
,只是返回 EXIT_FAILURE (1),所以这个答案可以应用于两个二进制文件。因为它也可以通过具有相同大小的两个可执行文件来确认:
唉,答案的直接问题
why are true and false so large?
可能是,因为不再有如此紧迫的理由来关心他们的最佳表现。它们对bash
性能不是必需的,不再被bash
(脚本)使用。类似的评论也适用于它们的大小,对于我们现在拥有的那种硬件来说,26KB 是微不足道的。对于典型的服务器/桌面来说,空间不再是宝贵的,他们甚至不再费心为
false
and使用相同的二进制文件true
,因为它只是在使用coreutils
.然而,本着这个问题的真正精神,为什么应该如此简单和小的东西会变得如此之大?
各部分的实际分布
/bin/true
如下图所示;主要代码+数据在 26KB 二进制文件中大约占 3KB,占/bin/true
.true
多年来,该实用程序确实获得了更多杂乱无章的代码,最引人注目的是对--version
和的标准支持--help
。然而,它不是(唯一)它如此大的主要理由,而是在动态链接(使用共享库)的同时,还具有
coreutils
作为静态库链接的二进制文件常用的通用库的一部分。用于构建elf
可执行文件的元数据也占二进制文件的很大一部分,按照今天的标准,它是一个相对较小的文件。其余的答案是解释我们如何构建以下图表,详细说明
/bin/true
可执行二进制文件的组成以及我们如何得出该结论。正如@Maks 所说,二进制文件是从 C 编译的;根据我的评论,也证实它来自 coreutils。我们直接指向作者 git https://github.com/wertarbyte/coreutils/blob/master/src/true.c,而不是像 @Maks 那样的 gnu git(相同的来源,不同的存储库 - 这个存储库被选中是因为它具有
coreutils
库的完整来源)我们可以在这里看到二进制文件的各种构建块
/bin/true
(Debian 9 - 64 bits fromcoreutils
):那些:
在 24KB 中,大约 1KB 用于固定 58 个外部函数。
这仍然为其余代码留下了大约 23KB 的空间。我们将在下面展示实际的主文件 - main()+usage() 代码大约编译了 1KB,并解释了其他 22KB 的用途。
用 进一步深入二进制文件
readelf -S true
,我们可以看到虽然二进制文件是 26159 字节,但实际编译的代码是 13017 字节,其余的是各种数据/初始化代码。然而,
true.c
这还不是全部,如果只是那个文件,13KB 似乎有点过分了;我们可以看到调用的函数main()
没有在精灵中看到的外部函数中列出objdump -T true
;存在于:那些未在外部链接的额外功能
main()
是:所以我的第一个怀疑是部分正确的,当库使用动态库时,
/bin/true
二进制文件很大*因为它包含一些静态库*(但这不是唯一的原因)。编译 C 代码通常不会因为这样的空间下落不明而效率低下,因此我最初怀疑有些不对劲。
额外的空间,几乎是二进制文件大小的 90%,确实是额外的库/elf 元数据。
在使用 Hopper 反汇编/反编译二进制以了解函数在哪里时,可以看到 true.c/usage() 函数的编译二进制代码实际上是 833 字节,而 true.c/main() 函数的编译二进制代码是 225字节,大约略小于 1KB。隐藏在静态库中的版本函数的逻辑大约为 1KB。
实际编译的 main()+usage()+version()+strings+vars 只用了大约 3KB 到 3.5KB。
确实具有讽刺意味的是,由于上述原因,如此小而不起眼的公用事业变得更大了。
相关问题:了解 Linux 二进制文件在做什么
true.c
main() 与有问题的函数调用:二进制文件各个部分的十进制大小:
的输出
readelf -S true
objdump -T true
(运行时动态链接的外部函数)的输出该实现可能来自 GNU coreutils。这些二进制文件是从 C 编译的;没有特别努力使它们小于默认值。
您可以尝试编译
true
自己的简单实现,您会注意到它的大小已经只有几 KB。例如,在我的系统上:当然,您的二进制文件更大。那是因为它们还支持命令行参数。尝试运行
/usr/bin/true --help
或/usr/bin/true --version
.除了字符串数据外,二进制文件还包括解析命令行标志等的逻辑。显然,这加起来大约有 20 KB 的代码。
作为参考,您可以在此处找到源代码:http: //git.savannah.gnu.org/cgit/coreutils.git/tree/src/true.c
Stripping them down to core functionality and writing in assembler yields far smaller binaries.
Original true/false binaries are written in C, which by its nature pulls in various library + symbol references. If you run
readelf -a /bin/true
this is quite noticeable.352 bytes for a stripped ELF static executable (with room to save a couple bytes by optimizing the asm for code-size).
Or, with a bit of a nasty/ingenious approach (kudos to stalkr), create your own ELF headers, getting it down to
132127 bytes. We're entering Code Golf territory here.Pretty big on my Ubuntu 16.04 too. exactly the same size? What makes them so big?
(excerpt:)
Ah, there is help for true and false, so let's try it:
Nothing. Ah, there was this other line:
So on my system, it's /bin/true, not /usr/bin/true
So there is help, there is version information, binding to a library for internationalization. This explains much of the size, and the shell uses its optimized command anyway and most of the time.
The accepted answer by Rui F Ribeiro gives a lot of good information, but it's missing some and I feel like it leaves the reader with a misleading impression that the code size is small relative to "ELF overhead" and similar.
Static linking is at object file granularity (or even finer with
--gc-sections
), so it doesn't make sense to talk about a static library being "linked in"; the only part linked in is what's used, and the code size here is code that's actually (gratuitously) used by the coreutils version oftrue
. It does not make sense to count it separately from what appears intrue.c
.The ELF metadata is pretty much entirely tables necessary for dynamic linking, and is nowhere near 90% of the size. These are the lines from
size -A
output relevant to it:for a total of around 5.5k, or an average of about 100 bytes per dynamic symbol (not quite fair because some aren't on a per-symbol basis, but it's a somewhat meaningful figure).
One large contributor to size that Rui's answer didn't cover is:
This 3.5k is DWARF unwind tables for C++ exception handling, introspective backtrace, etc. They're completely useless for
true
, but included by GCC policy in all output unless you explicitly omit them with a very verbosely-named option-fno-asynchronous-unwind-tables
. The motivations behind this are explained in an answer by me on Stack Overflow.So I would describe the final breakdown as:
特别值得注意的是,如果动态链接库所需的代码量足够少,静态链接可能比动态链接少。