【问题标题】:gcc / ld: overlapping sections (.tbss, .init_array) in statically-linked ELF binarygcc / ld:静态链接的 ELF 二进制文件中的重叠部分(.tbss、.init_array)
【发布时间】:2014-10-19 11:55:32
【问题描述】:

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

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,因此它不能放置在0x6b4060 之前。

生成的文件运行正常,但我仍然不完全了解它是如何工作的。从我在 glibc 文档中读到的内容来看,.tbss 只是用于线程本地存储的.bss 部分(即分配的内存暂存空间,并未真正映射到物理文件中)。是不是.tbss部分太特别了,可以和其他部分重叠? .init_array.fini_array.jcr是不是就这么没用(比如TLS相关的代码运行后就不需要了),所以可以被bss覆盖?或者它是某种错误?

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

【问题讨论】:

  • 很可能我不会帮助你。但也许您应该阅读 AMD64 gcc ABI 参考。 x86-64.org/documentation/abi.pdf。我记得对线程本地存储的访问是由段寄存器 FS 或 GS​​ “前缀”的。如果我没记错的话,你的帖子将解决 FS:0x6b4020。
  • 确实,我记得在某处读到,线程本地存储将使用 FS 段前缀操作,这纯粹是 glibc 的约定(不是平台 ABI,不是 SysV 调用约定的一部分)...要去试试阅读 Drepper 关于 TLS 的手册...
  • 该部分被标记为 NOBITS,这意味着 ELF 文件中没有与之关联的数据,只有大小。对于 NOBITS 部分,偏移字段是正式填写的。这种节的另一个例子是.bss,零初始化数据节。只要我们知道该部分是零初始化的,就不需要在文件中实际存储零,知道大小就足够了。
  • @AndreyChernyakhovskiy:我对无意义的偏移(即 0xb4020)没有任何问题,我对重叠 in-memory 地址范围(即 0x6b4020)有问题。

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


【解决方案1】:

.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

【讨论】:

  • 谢谢!这正是我一直在寻找的 - SEC_THREAD_LOCAL 标志使这个部分变得特别(不是 .tbss 名称 - 事实上,它可以有任何名称)。清晰简洁的答案。抱歉迟到了赏金......
  • 你会不会碰巧知道如何获取 tbss 的大小并将其放入变量中?我正在使用-static -nostdlib 进行编译,并且有一些__thread 变量。我不知道我需要分配多少内存,所以我想知道我的代码是否有办法在运行时获取值。链接器是否将它放在某个变量中?我可以在 tbss 部分之前和之后放置一个标签来计算大小吗(len = labelAfterTBSS - labelBeforeTBBS)。我能做些什么?我试过问但没有得到答案stackoverflow.com/questions/69498912/…
  • @EricStotch 我认为链接器不会将任何保存部分信息的变量添加到最终的可执行文件中。而且您将很难告诉编译器修复.tbss 中的变量顺序。最好的办法是解析 ELF 文件结构,例如,使用 section-related functions of the BFD library
【解决方案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了解更多。

【讨论】:

  • 我不是在抱怨文件偏移,而是在抱怨重叠的平面内存地址。即.tbss.init_array.fini_array.jcr相交。您提到 .tbss 部分是“特殊的” - 为什么会这样?它是由名称(.tbss)决定的吗?如果是这样,还有其他这样的“特殊”部分吗?谁决定最终映射到 0x6b4020 的内容?
  • 扩展答案以涵盖内存偏移,添加参考链接
猜你喜欢
  • 2012-05-15
  • 1970-01-01
  • 2016-06-20
  • 1970-01-01
  • 2015-02-27
  • 2017-03-20
  • 1970-01-01
  • 2020-02-27
  • 1970-01-01
相关资源
最近更新 更多