为什么register_tm_clones和deregister_tm_clones引用了.bss部分之后的地址?这个内存分配在哪里?

时间:2016-12-22 01:25:03

标签: c memory-management objdump stm readelf

register_tm_clonesderegister_tm_clones引用了RW部分末尾的内存地址。这个记忆是如何被追踪的?

示例:在下面的示例中,deregister_tm_clones引用了内存地址0x601077,但我们分配的最后一个RW部分.bss0x601069开始并且大小为0x7,我们会加0x601070。所以引用显然已经过去分配给.bss部分了,应该在我们的堆空间中,但是谁来管理它。

objdump -d main
...
0000000000400540 <deregister_tm_clones>:
  400540:       b8 77 10 60 00          mov    $0x601077,%eax
  400545:       55                      push   %rbp
  400546:       48 2d 70 10 60 00       sub    $0x601070,%rax
  40054c:       48 83 f8 0e             cmp    $0xe,%rax
...

readelf -S main
...
[25] .data             PROGBITS         0000000000601040  00001040
   0000000000000029  0000000000000000  WA       0     0     16
[26] .bss              NOBITS           0000000000601069  00001069
   0000000000000007  0000000000000000  WA       0     0     1
[27] .comment          PROGBITS         0000000000000000  00001069
   0000000000000058  0000000000000001  MS       0     0     1
[28] .shstrtab         STRTAB           0000000000000000  000019f2
   000000000000010c  0000000000000000           0     0     1
[29] .symtab           SYMTAB           0000000000000000  000010c8
   00000000000006c0  0000000000000018          30    47     8
[30] .strtab           STRTAB           0000000000000000  00001788
   000000000000026a  0000000000000000           0     0     1

请注意,引用恰好始于.bss部分的末尾。当我检查使用gdb分配的内存时,我发现有足够的空间,所以它工作正常,但我不知道如何管理这个内存。

Start Addr         End Addr          Size        Offset objfile
0x400000           0x401000          0x1000      0x0 /home/nobody/main
0x600000           0x601000          0x1000      0x0 /home/nobody/main
0x601000           0x602000          0x1000      0x1000 /home/nobody/main
0x7ffff7a17000     0x7ffff7bd0000    0x1b9000    0x0 /usr/lib64/libc-2.23.so

我在其他任何部分都找不到其它参考资料。为.bss:

加载的段也没有为其保留空间
LOAD         0x0000000000000e10 0x0000000000600e10 0x0000000000600e10
             0x0000000000000259 0x0000000000000260  RW     200000

有人可以澄清这些功能吗?来源在哪里?我已经阅读了关于事务内存的所有引用,但是它们涵盖了编程而不是实现。我找不到删除此代码的编译器选项,当然除了-nostdlibs之外什么都没有。

也许是malloc的这些部分吗?对于不使用malloc,线程或STM的代码,我不确定是否同意这些应该链接到我的代码中。

另见What functions does gcc add to the linux ELF?

更多详情:

$ make main
cc -c -o main.o main.c
cc -o main main.o

$ which cc
/usr/bin/cc

$ cc --version
    cc (GCC) 6.2.1 20160916 (Red Hat 6.2.1-2)
Copyright (C) 2016 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

$ cc --verbose
Using built-in specs.
COLLECT_GCC=cc
COLLECT_LTO_WRAPPER=/usr/libexec/gcc/x86_64-redhat-linux/6.2.1/lto-wrapper
Target: x86_64-redhat-linux
Configured with: ../configure --enable-bootstrap
 --enable-languages=c,c++,objc,obj-c++,fortran,ada,go,lto
 --prefix=/usr --mandir=/usr/share/man --infodir=/usr/share/info
 --with-bugurl=http://bugzilla.redhat.com/bugzilla --enable-shared
 --enable-threads=posix --enable-checking=release --enable-multilib
 --with-system-zlib --enable-__cxa_atexit --disable-libunwind-exceptions
 --enable-gnu-unique-object --enable-linker-build-id
 --with-linker-hash-style=gnu --enable-plugin --enable-initfini-array
 --disable-libgcj --with-isl --enable-libmpx --enable-gnu-indirect-function
 --with-tune=generic --with-arch_32=i686 --build=x86_64-redhat-linux
Thread model: posix
gcc version 6.2.1 20160916 (Red Hat 6.2.1-2) (GCC)

1 个答案:

答案 0 :(得分:5)

这是gcc为deregister_tm_clones()生成的非常愚蠢的指针算术代码。它实际上并不访问这些地址的内存。

<强>摘要

没有对这些指针进行访问;他们只是充当地址标签,GCC对如何比较两个(重新定位的)地址感到愚蠢。

这两个函数是C和C ++中transaction support的一部分。有关详细信息,请参阅GNU libitm

<强>背景

我在x86-64上运行Ubuntu 16.04.3 LTS(Xenial Xerus),安装了GCC版本4.8.5,4.9.4,5.4.1,6.3.0和7.1.0。 register_tm_clones()deregister_tm_clones()/usr/lib/gcc/x86-64/VERSION/crtbegin.o汇编而来。对于所有版本,register_tm_clones()都可以(没有奇数地址)。对于版本4.9.4,5.4.1和6.3.0,deregister_tm_clones()的代码是相同的,并且包括非常奇怪的指针比较测试。 deregister_tm_clones()的代码在7.1.0中修复,这是一个简单的地址测试。

这两个函数的来源位于GCC来源的libgcc/crtstuff.c

在这台计算机上,objdump -t /usr/lib/gcc/ARCH/VERSION/crtbegin.o显示.tm_clone_table__TMC_LIST____TMC_END__,对于我上面提到的所有GCC版本,所以在GCC来源中,{{1} }和USE_TM_CLONE_REGISTRY已定义。因此,我们可以将C中的两个函数描述为

HAVE_GAS_HIDDEN

基本上,typedef void (*func_ptr) (void); extern void _ITM_registerTMCloneTable(void *, size_t); extern void _ITM_deregisterTMCloneTable(void *); static func_ptr __TMC_LIST__[] = { }; extern func_ptr __TMC_END__[]; void deregister_tm_clones(void) { void (*fn)(void); if (__TMC_LIST__ != __TMC_END__) { fn = _ITM_deregisterTMCloneTable; if (fn != NULL) fn(__TMC_LIST__); } } void register_tm_clones(void) { void (*fn)(void); size_t size; size = (__TMC_END__ - __TMC_LIST__) / 2; if (size > 0) { fn = _ITM_registerTMCloneTable; if (fn != NULL) fn(__TMC_LIST__, size); } } 是函数指针数组,__TMC_LIST__是数组中函数指针对的数量。如果数组不为空,则为size_ITM_registerTMCloneTable()的函数,该函数在_ITM_deregisterTMCloneTable()GNU libitm中定义。如果未定义libitm.a / _ITM_registerTMCloneTable符号,则重定位代码会将零作为其地址。

因此,当数组为空时,和/或_ITM_deregisterTMCloneTable / _ITM_registerTMCloneTable符号未定义时,代码什么都不做:只有一些花哨的指针算法。

请注意,代码不会从任何内存地址加载指针值。地址(_ITMderegisterTMCloneTable__TMC_LIST____TMC_END___ITM_registerTMCloneTable)由链接器/重定位器提供,作为代码中的直接32位字面值。 (这就是为什么,如果你看一下目标文件的反汇编,你只能看到地址的零。)

<强>研究

_ITM_deregisterTMCloneTable的问题代码一开始就出现了:

deregister_tm_clones

(这个特殊的例子来自于使用静态x86-64上的gcc-6.3.0编译C中的基本Hello,World!示例)。

如果我们查看相同二进制文件的标题标题(004008c0 <deregister_tm_clones>: 4008c0: b8 57 bb 6c 00 mov $0x6cbb57,%eax 4008c5: 55 push %rbp 4008c6: 48 2d 50 bb 6c 00 sub $0x6cbb50,%rax 4008cc: 48 83 f8 0e cmp $0xe,%rax 4008d0: 48 89 e5 mov %rsp,%rbp 4008d3: 76 1b jbe 4008f0 <deregister_tm_clones+0x30> 4008d5: b8 00 00 00 00 mov $0x0,%eax 4008da: 48 85 c0 test %rax,%rax 4008dd: 74 11 je 4008f0 <deregister_tm_clones+0x30> 4008df: 5d pop %rbp 4008e0: bf 50 bb 6c 00 mov $0x6cbb50,%edi 4008e5: ff e0 jmpq *%rax 4008e7: (9-byte NOP) 4008f0: 5d pop %rbp 4008f1: c3 retq 4008f2: (14-byte NOP) 400900: ),我们会发现地址objdump -h0x6cbb50实际上并未映射到任何细分受众群;该

0x6cbb5f

即。 24 .data 00001ad0 00000000006ca080 00000000006ca080 000ca080 2**5 25 .bss 00001878 00000000006cbb60 00000000006cbb60 000cbb50 2**5 涵盖地址.data0x6ca0800x6cbb4f涵盖.bss 0x6cbb60

似乎就像汇编代码使用无效的地址一样!

但是,0x6cd3d8地址非常有效,因为该地址(0x6cbb50)上有一个零大小的隐藏符号:

objdump -t

因为我静态编译了二进制文件,所以006cbb50 g O .data 0000000000000000 .hidden __TMC_END__ 符号是此处__TMC_END__段的一部分;通常,它在.data。在任何情况下都没关系,因为.bss符号的大小为零:我们可以将其地址用作我们想要的任何计算的一部分,我们只是不能取消引用它,因为它包含没有数据,没有大小。

这会在__TMC_END__函数中留下第一个重新定位的地址,在这种情况下为deregister_tm_clones

如果我们看看代码实际上对该值做了什么,事实证明,对于某些脑死亡原因,编译的二进制代码是必要的计算

0x0x6cbb57

由于使用的比较函数是带符号的比较,因此上述行为与

完全相同
long temporary = relocated__TMC_LIST__address + 7;
long difference = temporary - relocated__TMC_END__address;
if (difference <= 14)
    return;

在任何情况下,显而易见的是long temporary = relocated__TMC_LIST__address; long difference = temporary - relocated__TMC_END__address; if (difference <= 7) return; ,并且重新定位的地址在OP的二进制文件和上面的二进制文件中是相同的。

<强>附录

我不知道完全为什么GCC会生成

__TMC_LIST__ == __TMC_END__

而不是

if ((__TMC_END__ + 7) - __TMC_LIST <= 14)

但在GCC bug 77813 Marc Glisse确实提到它(前者)确实是GCC最终产生的。 (错误本身与此没有直接关系,因为它是关于GCC将表达式优化为零,仅影响libitm用户,并且很容易修复。)

此外,在gcc-6.3.0和gcc-7.1.0之间,当生成的代码丢弃了该无效时,函数的C源不会改变。改变的是GCC如何为这种指针比较生成代码(在某些情况下)。