.bss节零初始化variables占用elf文件中的空间吗?
如果我理解正确,ELF文件中的.bss
节用于为零初始化variables分配空间。 我们的工具链生成ELF文件,因此我的问题是: .bss
部分实际上是否必须包含所有这些零? 当我分配一个全局十兆字节的数组时,它看起来像是一个非常可怕的空间浪费,结果在ELF文件中产生了十兆字节的零。 我在这里看到什么错误?
自从我和ELF一起工作了一段时间。 但我想我仍然记得这个东西。 不,它不包含那些零。 如果你查看一个ELF文件程序头文件,那么你会看到每个头文件有两个数字:一个是文件的大小。 另一个是在虚拟内存中readelf -l ./a.out
时的大小( readelf -l ./a.out
):
Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align PHDR 0x000034 0x08048034 0x08048034 0x000e0 0x000e0 RE 0x4 INTERP 0x000114 0x08048114 0x08048114 0x00013 0x00013 R 0x1 [Requesting program interpreter: /lib/ld-linux.so.2] LOAD 0x000000 0x08048000 0x08048000 0x00454 0x00454 RE 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
types的头文件是在文件加载执行时复制到虚拟内存中的头文件。 其他头文件包含其他信息,如所需的共享库。 正如你所看到的, FileSize
和MemSiz
对于包含bss
部分(第二个LOAD
之一)的头部显着不同:
0x00104 (file-size) 0x61bac (mem-size)
对于这个例子代码:
int a[100000]; int main() { }
ELF规范指出,mem-size大于文件大小的段的部分只填充了虚拟内存中的零。 第二个LOAD
头的段到段映射如下所示:
03 .ctors .dtors .jcr .dynamic .got .got.plt .data .bss
所以还有其他一些部分。 对于C ++构造函数/析构函数。 对于Java同样的事情。 然后它包含.dynamic
部分的副本和其他有用的dynamic链接(我相信这是包含所需的共享库之间的其他东西)的地方。 之后,包含已初始化的全局variables和本地静态variables的.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默认保留了未初始化的全局variables,允许在程序中定义两次全局variables,而不会出现多重定义错误。 使用-fno-common
使GCC对目标文件使用.bss部分(对最终的链接可执行文件没有影响,因为正如你所看到的那样,它将会进入一个.bss的输出部分,这由链接器控制脚本 ,用ld -verbose
显示)。 但是这不应该吓到你,这只是一个内部的细节。 请参阅gcc的联机帮助页。
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
这通常是embedded式开发人员遇到的第一个惊喜之一…从不初始化静态为零。 运行时加载器(通常)负责这个。 只要您明确地初始化任何东西,就告诉编译器/链接器将数据包含在可执行映像中。
.bss
节不存储在可执行文件中。 在最常见的部分( .text
, .data
, .bss
)中,只有.text
(实际代码)和.data
(已初始化的数据)出现在ELF文件中。
这是正确的,.bss文件中没有物理存在,只是有关其大小的信息是dynamic加载程序为应用程序分配.bss节的信息。 作为仅用拇指规则LOAD,TLS段获取应用程序的内存,其余的用于dynamic加载程序。
关于静态可执行文件,bss部分也在可执行文件中给出空间
embedded式应用程序中没有加载器这是常见的。
苏曼