请考虑以下代码:
#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
看到它的转储时。在__do_global_ctors_aux
或其他任何地方都没有_init
来电,也没有__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 ()
答案 0 :(得分:1)
那么,如何调用构造函数?
如果您查看使用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遍历此数组并调用在那里找到的所有构造函数。
答案 1 :(得分:0)
详细信息是针对具体实施的,您不会提及您的实施。
某些实现使用的完全有效的策略是创建一个包含程序真实入口点的运行时库。该真正的入口点首先调用所有构造函数,然后调用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
很容易函数简单地调用该段中的所有函数,而编译器不必逐个枚举每个这样的函数。