__do_global_ctors_aux 未在 objdump 中显示

__do_global_ctors_aux not shown in objdump

考虑以下代码:

#include <stdio.h> 

void __attribute__ ((constructor)) a_constructor() 
{ 
    printf("%s\n", __func__); 
}

void __attribute__ ((constructor)) b_constructor() 
{ 
    printf("%s\n", __func__); 
} 

int main() 
{ 
    printf("%s\n",__func__); 
}

我将上面的代码编译为:gcc -ggdb prog2.c -o prog2。代码按预期运行。

a_constructor
b_constructor
main

但是当我使用 objdump -d prog2 > f 看到它的转储时。在 _init 或其他任何地方都没有对 __do_global_ctors_aux 的调用,也没有 __do_global_ctors_aux 的定义。那么,如何调用构造函数呢? __do_global_ctors_aux的定义在哪里?这是一些优化吗?

我也试过像这样没有优化地编译它:gcc -ggdb -O0 prog2.c -o prog2。请说清楚。 正在 32 位 linux 机器上进行编译。

编辑

我的 gdb bt 输出是:

Breakpoint 1, a_constructor () at prog2.c:5
5       printf("%s\n", __func__); 
(gdb) bt
#0  a_constructor () at prog2.c:5
#1  0x080484b2 in __libc_csu_init ()
#2  0xb7e31a1a in __libc_start_main (main=0x8048445 <main>, argc=1, argv=0xbffff014, init=0x8048460 <__libc_csu_init>, 
fini=0x80484d0 <__libc_csu_fini>, rtld_fini=0xb7fed180 <_dl_fini>, stack_end=0xbffff00c) at libc-start.c:246
#3  0x08048341 in _start ()

详细信息是特定于实现的,您没有提及您的实现。

一些实现使用的完全有效的策略是创建一个 运行-time 库,其中包含程序的实际入口点。那个真正的入口点首先调用所有构造函数,然后调用main。如果您的程序是动态链接的,并且该真正入口点背后的代码驻留在共享库中(例如,libc),那么显然反汇编您的程序不可能向您显示调用构造函数的位置。

一个简单的方法可以准确地确定调用来自何处,方法是将程序加载到调试器中,在其中一个构造函数上设置断点,并在命中断点时询问调用堆栈。例如,在 Cygwin 上:

$ gdb ./test
GNU gdb (GDB) 7.8
Copyright (C) 2014 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later 
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "i686-pc-cygwin".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
.
Find the GDB manual and other documentation resources online at:
.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from ./test...done.
(gdb) b a_constructor
Breakpoint 1 at 0x4011c6: file test.cc, line 5.
(gdb) run
Starting program: /home/Harald van Dijk/test
[New Thread 4440.0x1734]
[New Thread 4440.0xa8c]
b_constructor

Breakpoint 1, a_constructor () at test.cc:5
5           printf("%s\n", __func__);
(gdb) bt
#0  a_constructor () at test.cc:5
#1  0x61006986 in __main () from /usr/bin/cygwin1.dll
#2  0x004011f6 in main () at test.cc:14
(gdb)

这表明在 Cygwin 上,使用了我提到的策略的变体:真正的入口点是 main 函数,但编译器插入了对 Cygwin 特定 __main 的调用功能就在一开始,它是 __main 搜索所有构造函数并直接调用它们的函数。

(顺便说一下,如果 main 被递归调用,这显然会中断:构造函数将 运行 第二次。这就是为什么 C++ 不允许递归调用 main 的原因。 C 确实允许它,但是,标准 C 没有构造函数。)

并且您可以得到 __main 函数如何搜索它们的提示,方法是不反汇编可执行程序,而是向编译器询问生成的程序集:

$ gcc -S test.c -o -

我不会在这里复制整个程序集列表,但它表明在这个特定的实现中,构造函数在 .ctors 段中发出,因此 __main函数来简单地调用该段中的所有函数,而编译器不必一个一个地枚举每个这样的函数。

So, how do the constructors get called?

如果您查看使用 gcc -g -O0 -S -fverbose-asm prog2.c -o prog2.s 生成的反汇编,有以下内容:

    .text
.Ltext0:
    .globl  a_constructor
    .type   a_constructor, @function
a_constructor:
.LFB0:
    .file 1 "test.c"
    .loc 1 4 0
    .cfi_startproc
    pushq   %rbp    #
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp  #,
    .cfi_def_cfa_register 6
    .loc 1 5 0
    movl    $__func__.2199, %edi    #,
    call    puts    #
    .loc 1 6 0
    popq    %rbp    #
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE0:
    .size   a_constructor, .-a_constructor
    .section    .init_array,"aw"
    .align 8
    .quad   a_constructor

在上面,函数 a_constructor 被放入 .text 部分。指向该函数的指针也附加到 .init_array 部分。在调用 main 之前,glibc 遍历这个数组并调用在那里找到的所有构造函数。