gcc / ld:静态链接的ELF二进制文件中的重叠节(.tbss,.init_array)

时间:2014-08-26 08:14:52

标签: gcc ld static-linking elf thread-local-storage

我在x86_64机器上使用gcc版本4.8.2(Debian 4.8.2-21)在Debian 7系统上静态编译一个非常简单的hello-world单行程序:

gcc test.c -static -o test

我得到一个可执行的ELF文件,其中包含以下部分:

[17] .tdata            PROGBITS         00000000006b4000  000b4000
     0000000000000020  0000000000000000 WAT       0     0     8
[18] .tbss             NOBITS           00000000006b4020  000b4020
     0000000000000030  0000000000000000 WAT       0     0     8
[19] .init_array       INIT_ARRAY       00000000006b4020  000b4020
     0000000000000010  0000000000000000  WA       0     0     8
[20] .fini_array       FINI_ARRAY       00000000006b4030  000b4030
     0000000000000010  0000000000000000  WA       0     0     8
[21] .jcr              PROGBITS         00000000006b4040  000b4040
     0000000000000008  0000000000000000  WA       0     0     8
[22] .data.rel.ro      PROGBITS         00000000006b4060  000b4060
     00000000000000e4  0000000000000000  WA       0     0     32

请注意,.tbss部分分配在地址0x6b4020..0x6b4050(0x30字节),它与.init_array部分的分配相交,位于0x6b4020..0x6b4030(0x10字节),.fini_array部分位于0x6b4030..0x6b4040(0x10字节),.jcr部分位于0x6b4040..0x6b4048(8字节)。

请注意与以下部分相交,例如,.data.rel.ro,但这可能是因为.data.rel.ro对齐是32,因此它可以&# 39; t可以放在0x6b4060之前。

生成的文件运行正常,但我仍然没有完全了解它是如何工作的。从我在glibc文档中看到的内容,.tbss是线程本地存储的正常.bss部分(即分配的内存暂存空间,实际上没有映射到物理文件中)。是.tbss部分是如此特殊以至于它可以与其他部分重叠吗? .init_array.fini_array.jcr是如此无用(例如,它们不再需要它们,然后运行与TLS相关的代码),所以它们可以被bss覆盖吗?或者它是某种错误?

基本上,如果我尝试在我的应用程序中读取地址0x6b4020,我会阅读和写入什么? .tbss内容或.init_array指针?为什么呢?

2 个答案:

答案 0 :(得分:5)

.tbss的虚拟地址没有意义,因为该部分仅用作GLIBC中线程实现分配的TLS存储模板。

此虚拟地址的实现方式是.tbss在默认链接描述文件中跟.tbdata

...
.gcc_except_table   : ONLY_IF_RW { *(.gcc_except_table .gcc_except_table.*) }
/* Thread Local Storage sections  */
.tdata          : { *(.tdata .tdata.* .gnu.linkonce.td.*) }
.tbss           : { *(.tbss .tbss.* .gnu.linkonce.tb.*) *(.tcommon) }
.preinit_array     :
{
  PROVIDE_HIDDEN (__preinit_array_start = .);
  KEEP (*(.preinit_array))
  PROVIDE_HIDDEN (__preinit_array_end = .);
}
.init_array     :
{
   PROVIDE_HIDDEN (__init_array_start = .);
   KEEP (*(SORT(.init_array.*)))
   KEEP (*(.init_array))
   PROVIDE_HIDDEN (__init_array_end = .);
}
...

因此,它的虚拟地址只是前一部分的虚拟地址(.tbdata)加上前一部分的大小(最终需要一些填充以达到所需的对齐)。 .init_array(或.preinit_array如果存在)接下来应该以相同的方式确定其位置,但.tbss已知非常特殊,它被赋予了深刻的硬编码GNU LD内部的治疗:

/* .tbss sections effectively have zero size.  */
if ((os->bfd_section->flags & SEC_HAS_CONTENTS) != 0
    || (os->bfd_section->flags & SEC_THREAD_LOCAL) == 0
    || link_info.relocatable)
  dotdelta = TO_ADDR (os->bfd_section->size);
else
  dotdelta = 0;    // <----------------
dot += dotdelta;

.tbss不可重定位,它设置了SEC_THREAD_LOCAL标志,并且没有内容(NOBITS),因此采用了else分支。换句话说,无论.tbss有多大,链接器都不会提前跟随它的部分的位置(也称为“点”)。

另请注意.tbss位于不可加载的ELF段中:

Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  LOAD           0x0000000000000000 0x0000000000400000 0x0000000000400000
                 0x00000000000b1f24 0x00000000000b1f24  R E    200000
  LOAD           0x00000000000b2000 0x00000000006b2000 0x00000000006b2000
                 0x0000000000002288 0x00000000000174d8  RW     200000
  NOTE           0x0000000000000158 0x0000000000400158 0x0000000000400158
                 0x0000000000000044 0x0000000000000044  R      4
  TLS            0x00000000000b2000 0x00000000006b2000 0x00000000006b2000 <---+
                 0x0000000000000020 0x0000000000000060  R      8              |
  GNU_STACK      0x0000000000000000 0x0000000000000000 0x0000000000000000     |
                 0x0000000000000000 0x0000000000000000  RW     8              |
                                                                              |
 Section to Segment mapping:                                                  |
  Segment Sections...                                                         |
   00     .note.ABI-tag ...                                                   |
   01     .tdata .ctors ...                                                   |
   02     .note.ABI-tag ...                                                   |
   03     .tdata .tbss    <---------------------------------------------------+
   04

答案 1 :(得分:2)

如果您对两件事情有所了解,这很简单:

1)什么是SHT_NOBITS

2)什么是tbss部分

SHT_NOBITS表示此部分在文件中不占用空间。

通常情况下,NOBITS部分(如bss)会在加载段末尾的所有PROGBITS部分之后放置。

tbss是保存未初始化的线程局部数据的特殊部分,这些数据有助于程序的内存映像。请注意:此部分必须为每个程序线程保存唯一数据。

现在让我们谈谈重叠。我们有两种可能的重叠 - 内部二进制文件和内部存储器。

1)二进制文件偏移:

在二进制文件中没有要在此部分下写入的数据。在文件内部它没有空间,所以链接器在tbss声明后立即启动下一节init_array。你可以考虑它的大小不是关于大小,而是关于代码的特殊服务信息,如:

if (isTLSSegment) tlsStartAddr += section->memSize();

因此它不会重叠文件内的任何内容。

2)内存偏移

动态链接器可能会在启动时修改tdata和tbss部分 执行重定位,但之后,节数据作为初始化图像保留,不再修改。对于每个线程,包括初始线程,分配新存储器,然后复制初始化图像的内容。这可确保所有线程获得相同的起始条件。

这使得tbss(和tdata)如此特别。

不要将他们的内存偏移视为静态知识 - 它们更像是每线程工作的“生成模式”。因此它们也不能与“正常”内存偏移重叠 - 它们正以其他方式处理。

您可以咨询this paper以了解详情。