Рассмотрим следующий код:
#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 ()
Итак, как же вызывать конструкторы?
Если вы посмотрите на разборку, произведенную с 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 перебирает этот массив и вызывает все найденные там функции конструктора.
Детали зависят от реализации, и вы не упоминаете свою реализацию.
Совершенно правильная стратегия, используемая некоторыми реализациями, заключается в создании библиотеки времени выполнения, которая содержит реальную точку входа для вашей программы. Эта реальная точка входа сначала вызывает все конструкторы, а затем вызывает main
, Если ваша программа динамически связана, а код этой реальной точки входа находится в общей библиотеке (например, libc
), то четкая дизассемблирование вашей программы не может показать вам, где вызывается конструктор.
Простой способ выяснить, откуда именно происходит вызов, — это загрузить вашу программу в отладчик, установить точку останова на одном из конструкторов и запросить стек вызовов при достижении точки останова. Например, на Cygwin:
$ gdb ./test GNU GDB (GDB) 7,8 Copyright (C) 2014 Free Software Foundation, Inc. Лицензия GPLv3 +: GNU GPL версии 3 или более поздней Это бесплатное программное обеспечение: вы можете свободно изменять и распространять его. НЕ ПРЕДОСТАВЛЯЕТСЯ ГАРАНТИИ, если это разрешено законом. Введите «показать копирование» и «показать гарантию» для деталей. Этот GDB был настроен как «i686-pc-cygwin». Напечатайте "show configuration" для деталей конфигурации. Инструкции по сообщению об ошибках смотрите: , Найдите руководство GDB и другие источники документации в Интернете по адресу: , Для получения справки введите «помощь». Введите «apropos word» для поиска команд, связанных с «word» ... Чтение символов из ./test...done. (gdb) b a_constructor Точка останова 1 в 0x4011c6: файл test.cc, строка 5. (GDB) запустить Стартовая программа: / home / Харальд ван Дейк / test [Новая тема 4440.0x1734] [Новая тема 4440.0xa8c] b_constructor Точка останова 1, a_constructor () в test.cc:5 5 printf ("% s \ n", __func__); (GDB) Bt # 0 a_constructor () в test.cc:5 # 1 0x61006986 в __main () из /usr/bin/cygwin1.dll # 2 0x004011f6 в main () в test.cc:14 (GDB)
Это показывает, что в Cygwin используется вариант стратегии, о которой я говорил: реальная точка входа — это main
функция, но компилятор вставляет вызов в Cygwin __main
функционировать в самом начале, и это то, что __main
функция, которая ищет все конструкторы и вызывает их напрямую.
(Кстати, явно это сломается, если main
вызывается рекурсивно: конструкторы запускаются второй раз. Вот почему C ++ не позволяет main
быть вызванным рекурсивно. C допускает это, но тогда стандартный C не имеет функций конструктора.)
И вы можете получить подсказку о том, как это __main
функция ищет их, не разбирая исполняемую программу, а запрашивая сгенерированную сборку у компилятора:
$ gcc -S test.c -o -
Я не буду копировать весь список сборки здесь, но он показывает, что в этой конкретной реализации функции конструктора передаются в виде .ctors
сегмент, так что было бы легко для __main
Функция просто вызывает все функции в этом сегменте, без необходимости компиляции перечислять каждую такую функцию одну за другой.