.bss 部分零初始化变量是不是占用 elf 文件中的空间?
Posted
技术标签:
【中文标题】.bss 部分零初始化变量是不是占用 elf 文件中的空间?【英文标题】:Do .bss section zero initialized variables occupy space in elf file?.bss 部分零初始化变量是否占用 elf 文件中的空间? 【发布时间】:2010-10-11 06:34:46 【问题描述】:如果我理解正确,ELF 文件中的.bss
部分用于为零初始化变量分配空间。我们的工具链生成 ELF 文件,因此我的问题是:.bss
部分实际上是否必须包含所有这些零?这似乎是一种可怕的空间浪费,例如,当我分配一个全局 10 兆字节的数组时,它会在 ELF 文件中产生 10 兆字节的零。我在这里看错了什么?
【问题讨论】:
快速回答问题的方法:使用int is[1000000]
创建一个hello world,另一个没有,编译并查看编译后的大小:-) 然后真正理解,使用binutils 反编译,或编译为程序集带有-S
的代码。
【参考方案1】:
自从我与 ELF 合作以来已经有一段时间了。但我想我仍然记得这些东西。不,它实际上不包含那些零。如果你查看一个 ELF 文件程序头,你会看到每个头都有两个数字:一个是文件的大小。另一个是该部分在虚拟内存中分配时的大小 (readelf -l ./a.out
):
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
PHDR 0x000034 0x08048034 0x08048034 0x000e0 0x000e0 R E 0x4
INTERP 0x000114 0x08048114 0x08048114 0x00013 0x00013 R 0x1
[Requesting program interpreter: /lib/ld-linux.so.2]
LOAD 0x000000 0x08048000 0x08048000 0x00454 0x00454 R E 0x1000
LOAD 0x000454 0x08049454 0x08049454 0x00104 0x61bac RW 0x1000
DYNAMIC 0x000468 0x08049468 0x08049468 0x000d0 0x000d0 RW 0x4
NOTE 0x000128 0x08048128 0x08048128 0x00020 0x00020 R 0x4
GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RW 0x4
LOAD
类型的标头是在加载文件执行时复制到虚拟内存中的标头。其他标头包含其他信息,例如所需的共享库。如您所见,FileSize
和 MemSiz
对于包含 bss
部分的标头(第二个 LOAD
之一)明显不同:
0x00104 (file-size) 0x61bac (mem-size)
对于这个示例代码:
int a[100000];
int main()
ELF 规范说,段中内存大小大于文件大小的部分只是在虚拟内存中用零填充。第二个LOAD
标头的段到段映射如下:
03 .ctors .dtors .jcr .dynamic .got .got.plt .data .bss
所以那里也有一些其他部分。对于 C++ 构造函数/析构函数。 Java 也是如此。然后它包含.dynamic
部分的副本和其他对动态链接有用的东西(我相信这是包含所需共享库的地方)。之后,.data
部分包含已初始化的全局变量和局部静态变量。最后,出现.bss
部分,在加载时用零填充,因为文件大小没有覆盖它。
顺便说一句,您可以使用-M
链接器选项查看特定符号将被放置到哪个输出部分。对于 gcc,您使用 -Wl,-M
将选项传递给链接器。上面的示例显示a
分配在.bss
内。它可能会帮助您验证您的未初始化对象是否真的以.bss
而不是其他地方结束:
.bss 0x08049560 0x61aa0
[many input .o files...]
*(COMMON)
*fill* 0x08049568 0x18 00
COMMON 0x08049580 0x61a80 /tmp/cc2GT6nS.o
0x08049580 a
0x080ab000 . = ALIGN ((. != 0x0)?0x4:0x1)
0x080ab000 . = ALIGN (0x4)
0x080ab000 . = ALIGN (0x4)
0x080ab000 _end = .
GCC 默认将未初始化的全局变量保存在 COMMON 部分,以与旧编译器兼容,允许在程序中定义两次全局变量而不会出现多个定义错误。使用-fno-common
使 GCC 将 .bss 部分用于目标文件(对最终链接的可执行文件没有影响,因为正如您所见,它无论如何都会进入 .bss 输出部分。这由 链接器脚本。用ld -verbose
显示它)。但这不应该吓到你,这只是一个内部细节。请参阅 gcc 的联机帮助页。
【讨论】:
我认为 NOBITS 部分类型必须设置为允许这样做? 沃特。嗯,我从来没有用过那个标志。我的 gcc 机器头文件看起来像 #define BSS_SECTION_ASM_OP "\t.section\t.bss, \"aw\"" 你说得对——在 gcc 中,知道符号应该进入 .bss 就足够了。 ld 将注意在此部分设置 NOBITS,从而指示“文件中没有数据 - 将其归零”。 很好解释。我还有一个问题:你知道使用哪个工具包来转储所有静态或全局变量的地址吗?例如,在上面的代码中,工具包应该将地址“a”作为“0x08049580”。我尝试了 readelf -s,但它仅适用于单个 obj,而我想从最终的可执行文件中获取这些地址。谢谢! 很好的解释。你从哪里得到所有这些信息的?有没有推荐的人/文档/书?【参考方案2】:ELF 文件中的.bss
部分用于以编程方式未初始化但保证在运行时设置为零的静态数据。这里有一个小例子来解释这个区别。
int main()
static int bss_test1[100];
static int bss_test2[100] = 0;
return 0;
在这种情况下,bss_test1
被放入 .bss
,因为它未初始化。 bss_test2
但是与一堆零一起放入 .data
段中。运行时加载器基本上分配为.bss
保留的空间量,并在任何用户空间代码开始执行之前将其清零。
您可以使用objdump
、nm
或类似的实用程序查看差异:
moozletoots$ objdump -t a.out | grep bss_test
08049780 l O .bss 00000190 bss_test1.3
080494c0 l O .data 00000190 bss_test2.4
这通常是嵌入式开发人员遇到的第一个惊喜...永远不要将静态变量显式初始化为零。运行时加载器(通常)会处理这个问题。一旦明确初始化任何内容,您就是在告诉编译器/链接器将数据包含在可执行映像中。
【讨论】:
在我的平台 gcc 上将 bss_test2 放入 .bss 部分。你可以提到控制这个的 -fno-zero-initialized-in-bss 编译选项。 来自手册:“如果目标支持 BSS 部分,GCC 默认将初始化为零的变量放入 BSS。”【参考方案3】:.bss
部分未存储在可执行文件中。在最常见的部分(.text
、.data
、.bss
)中,ELF 文件中只有.text
(实际代码)和.data
(初始化数据)。
【讨论】:
这不是任意可执行文件上的 readelf 告诉我的。文件中有大量的部分,包括 .bss 部分。 它不依赖于 ELF 本身,而是依赖于您的编译链(语言、工具、调试等选项......)。您还可以拥有自己的自定义部分。.bss
部分至少存储在 ELF 的可执行文件中。但是它的内容并没有被存储,所以文件中.bss
的大小是一个很小的常数。在具有内存保护的操作系统上,.bss
部分需要以某种方式存储,以便加载程序可以在该位置安排可写内存。当然可以想象,.bss
在某些格式中剩下的所有内容都是对已分配但未复制的大小字段的贡献。【参考方案4】:
没错,.bss 在文件中实际上并不存在,而只是存在有关其大小的信息,以便动态加载程序为应用程序分配 .bss 部分。 经验法则只有 LOAD,TLS Segment 为应用程序获取内存,其余用于动态加载。
关于静态可执行文件,bss部分在可执行文件中也有空间
没有加载器的嵌入式应用程序很常见。
苏曼
【讨论】:
你说,TLS也被加载了,作为PT_LOAD?我看到 PT_TLS 包含在 PT_LOAD以上是关于.bss 部分零初始化变量是不是占用 elf 文件中的空间?的主要内容,如果未能解决你的问题,请参考以下文章