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的代码,我不确定是否同意这些应该链接到我的代码中。
另见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)
答案 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 -h
到0x6cbb50
实际上并未映射到任何细分受众群;该
0x6cbb5f
即。 24 .data 00001ad0 00000000006ca080 00000000006ca080 000ca080 2**5
25 .bss 00001878 00000000006cbb60 00000000006cbb60 000cbb50 2**5
涵盖地址.data
至0x6ca080
,0x6cbb4f
涵盖.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如何为这种指针比较生成代码(在某些情况下)。