c - 为什么 register

c - 为什么 register

它只是 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_REGISTRY和HAVE_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 :

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

4008d5: b8 00 00 00 00 mov $0x0,%eax

4008da: 48 85 c0 test %rax,%rax

4008dd: 74 11 je 4008f0

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如果我们查看相同二进制文件的节头( ),我们会发现地址实际上没有映射到任何段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;

在任何情况下,很明显__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 如何为这种指针比较生成代码(在某些情况下)。

关于作者: admin

相关推荐

荣耀手机屏幕碎裂如何优惠维修?
世界杯男子排球历届冠军,世界杯男排各队名单
《王者荣耀》礼包哪里领取 ios及安卓礼包分享