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

有帮助吗?

解决方案

自从我与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规范说,mem-size大于文件大小的段的部分只是在虚拟内存中用零填充。第二个 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的联机帮助页。

其他提示

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

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

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

这是正确的,.bss在物理上不存在于文件中,而是仅存在有关其大小的信息,动态加载程序为应用程序分配.bss部分。 由于拇指规则只有LOAD,TLS Segment获取应用程序的内存,其余用于动态加载器。

关于静态可执行文件,bss部分也在可执行的

中给出了空间

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

苏曼

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top