46

如果我理解正确,.bssELF 文件中的部分用于为零初始化变量分配空间。我们的工具链生成 ELF 文件,因此我的问题是:该.bss部分实际上是否必须包含所有这些零?这似乎是一种可怕的空间浪费,例如,当我分配一个全局 10 兆字节的数组时,它会在 ELF 文件中产生 10 兆字节的零。我在这里看错了什么?

4

4 回答 4

69

自从我与 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的标头是在加载文件以执行时复制到虚拟内存中的标头。其他标头包含其他信息,例如所需的共享库。如您所见,包含该部分(第二个)的标题的FileSizeMemSiz显着不同:bssLOAD

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 的联机帮助页。

于 2009-03-04T14:21:19.933 回答
21

ELF 文件中的.bss部分用于未以编程方式初始化但保证在运行时设置为零的静态数据。这里有一个小例子来解释这个区别。

int main() {
    static int bss_test1[100];
    static int bss_test2[100] = {0};
    return 0;
}

在这种情况下,由于它未初始化,因此bss_test1被放入。但是与一堆零一起放入该段中。运行时加载器基本上分配为 保留的空间量,并在任何用户态代码开始执行之前将其清零。.bssbss_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

这通常是嵌入式开发人员遇到的第一个惊喜……永远不要显式地将静态初始化为零。运行时加载器(通常)会处理这个问题。一旦您明确初始化任何内容,您就是在告诉编译器/链接器将数据包含在可执行映像中。

于 2009-03-04T14:28:53.753 回答
3

.bss节不存储在可执行文件中。在最常见的部分(.text.data.bss)中,只有.text(实际代码)和.data(初始化数据)存在于 ELF 文件中。

于 2009-03-04T14:19:26.770 回答
1

没错,.bss 在文件中实际上并不存在,而只是存在有关其大小的信息,以便动态加载程序为应用程序分配 .bss 部分。由于拇指规则只有 LOAD,TLS Segment 为应用程序获取内存,其余用于动态加载程序。

关于静态可执行文件,在可执行文件中也给了 bss 部分空间

没有加载程序的嵌入式应用程序很常见。

苏曼

于 2009-03-27T17:38:19.317 回答