7

register_tm_clones并且deregister_tm_clones正在引用我的 RW 部分末尾的内存地址。这个内存是如何被追踪的?

示例:在下面的示例中,deregister_tm_clones引用了内存地址0x601077,但我们分配的最后一个 RW 部分,.bss开始于0x601069并具有大小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 的代码,我不确定我是否同意这些应该链接到我的代码中。

另请参阅gcc 为 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)
4

1 回答 1

11

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

概括

没有对这些指针进行访问;它们只是充当地址标签,而 GCC 对于如何比较两个(重新定位的)地址很傻。

这两个函数是C 和 C++中事务支持的一部分。有关更多详细信息,请参阅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中。

在这台机器上,对于我上面提到的所有 GCC 版本,objdump -t /usr/lib/gcc/ARCH/VERSION/crtbegin.o显示.tm_clone_table__TMC_LIST____TMC_END__,所以在 GCC 源代码中,USE_TM_CLONE_REGISTRYHAVE_GAS_HIDDEN都被定义了。因此,我们可以将 C 中的两个函数描述为

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()定义在libitm.aGNU libitm中的函数。当_ITM_registerTMCloneTable/_ITM_deregisterTMCloneTable符号未定义时,重定位代码产生零作为它们的地址。

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

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

调查

有问题的代码deregister_tm_clones出现在最开始:

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:

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

objdump -h如果我们查看相同二进制文件的节头( ),我们会发现地址实际上没有映射到任何段0x6cbb500x6cbb5f

24 .data  00001ad0  00000000006ca080  00000000006ca080  000ca080  2**5
25 .bss   00001878  00000000006cbb60  00000000006cbb60  000cbb50  2**5

.data涵盖地址0x6ca0800x6cbb4f,并.bss涵盖 0x6cbb600x6cd3d8

汇编代码似乎使用了无效地址!

但是,该地址是非常有效的,因为在该地址 ( )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;

在任何情况下,很明显__TMC_LIST__ == __TMC_END__和 重定位的地址在 OP 的二进制文件和上面的二进制文件中都是相同的。

附录

我不知道为什么GCC 会生成

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

而不是

if (__TMC_END__ <= __TMC_LIST__)

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

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

于 2017-08-11T02:16:00.327 回答