понимание __libc_init_array

Я просмотрел исходный код __libc_init_array с http://newlib.sourcearchive.com/documentation/1.18.0/init_8c-source.html.
Но я не совсем понимаю, что делает эта функция.

Я знаю, что эти символы

/* These magic symbols are provided by the linker.  */
extern void (*__preinit_array_start []) (void) __attribute__((weak));
extern void (*__preinit_array_end []) (void) __attribute__((weak));
extern void (*__init_array_start []) (void) __attribute__((weak));
extern void (*__init_array_end []) (void) __attribute__((weak));
extern void (*__fini_array_start []) (void) __attribute__((weak));
extern void (*__fini_array_end []) (void) __attribute__((weak));

определяется в сценарии компоновщика.
Часть скрипта компоновщика может выглядеть так:

  .preinit_array     :
  {
    PROVIDE_HIDDEN (__preinit_array_start = .);
    KEEP (*(.preinit_array*))
    PROVIDE_HIDDEN (__preinit_array_end = .);
  } >FLASH
  .init_array :
  {
    PROVIDE_HIDDEN (__init_array_start = .);
    KEEP (*(SORT(.init_array.*)))
    KEEP (*(.init_array*))
    PROVIDE_HIDDEN (__init_array_end = .);
  } >FLASH
  ...

а затем я поискал с ключом "init_array" в документах ELF-v1.1, gcc 4.7.2, ld и codeourcery (я использую codeourcery g ++ lite), но ничего не получил.

Где я могу найти описание этих символов?


person Pony279    schedule 07.03.2013    source источник


Ответы (4)


Эти символы связаны с кодом запуска конструктора C / C ++ и деструктора, который вызывается до / после main(). Разделы с именами .init, .ctors, .preinit_array и .init_array предназначены для инициализации объектов C / C ++, а разделы .fini, .fini_array и .dtors предназначены для удаления. Символы начала и конца определяют начало и конец разделов кода, связанных с такими операциями, и на них могут ссылаться другие части кода поддержки среды выполнения.

Разделы .preinit_array и .init_array содержат массивы указателей на функции, которые будут вызываться при инициализации. .fini_array - это массив функций, которые будут вызываться при уничтожении. Предположительно, начальная и конечная метки используются для просмотра этих списков.

Хороший пример кода, в котором используются эти символы, можно найти здесь источник libc для initfini.c. Вы можете видеть, что при запуске вызывается __libc_init_array(), и он сначала вызывает все указатели функций в разделе .preinit_array, ссылаясь на начальную и конечную метки. Затем он вызывает функцию _init() в разделе .init. Наконец, он вызывает все указатели функций в разделе .init_array. После завершения main() вызов teardown в __libc_fini_array() вызывает все функции в .fini_array перед окончательным вызовом _fini(). Обратите внимание, что в этом коде, похоже, есть ошибка вырезания и вставки, когда он вычисляет количество функций, вызываемых при разрыве. Предположительно они имели дело с ОС микроконтроллера реального времени и никогда не сталкивались с этим разделом.

person Robotbugs    schedule 22.06.2015
comment
Кроме того, i следует подписать __libc_fini_array, чтобы цикл завершился (вероятно, это не так). - person starblue; 14.01.2021

Ответ от @Robotbugs интересен, но я нашел дополнительную информацию, которая может удовлетворить любопытство других.

Двоичный интерфейс приложения System V, похоже, применим к исполняемым файлам, созданным gcc (и Наверное, какие-то другие компиляторы - на ум приходит clang).

В главе о специальных разделах говорится (только соответствующие части, измененные мной ):

.preinit_array:

Этот раздел содержит массив указателей на функции, который вносит вклад в единый массив предварительной инициализации для исполняемого или общего объекта, содержащего раздел.

.init_array

Этот раздел содержит массив указателей на функции, который вносит вклад в единый массив инициализации для исполняемого или общего объекта, содержащего раздел.

.fini_array

Этот раздел содержит массив указателей на функции, который вносит вклад в единый массив завершения для исполняемого или общего объекта, содержащего раздел.

Файл init.c из newlib включает:

/* Iterate over all the init routines.  */
void
__libc_init_array (void)
{
    size_t count;
    size_t i;

    count = __preinit_array_end - __preinit_array_start;
    for (i = 0; i < count; i++)
        __preinit_array_start[i] ();

#ifdef HAVE_INIT_FINI
    _init ();
#endif

    count = __init_array_end - __init_array_start;
    for (i = 0; i < count; i++)
    __init_array_start[i] ();
}

Это соответствует каноническому решению сценария компоновщика для процессоров STM32 (в качестве примера):

.preinit_array     :
{
    PROVIDE_HIDDEN (__preinit_array_start = .);
    KEEP (*(.preinit_array*))
    PROVIDE_HIDDEN (__preinit_array_end = .);
} >FLASH
.init_array :
{
    PROVIDE_HIDDEN (__init_array_start = .);
    KEEP (*(SORT(.init_array.*)))
    KEEP (*(.init_array*))
    PROVIDE_HIDDEN (__init_array_end = .);
} >FLASH
.fini_array :
{
    PROVIDE_HIDDEN (__fini_array_start = .);
    KEEP (*(SORT(.fini_array.*)))
    KEEP (*(.fini_array*))
    PROVIDE_HIDDEN (__fini_array_end = .);
} >FLASH

Эта часть сценария компоновщика довольно ясна: она определяет символы, необходимые Newlib для выполнения функций массива, указанных в двоичный интерфейс приложения System V для preinit и init. Кажется, это стандартное решение для статических конструкторов в C ++. И fini соответствует статическим деструкторам.

Самая ироничная часть этой истории, конечно, заключается в том, что использование статических объектов C ++ без Construct On First Use Idiom - лучший способ получить статический порядок инициализации. проблема! Т.е. На практике объекты C ++ никогда не должны создаваться с использованием массива _6 _ / _ 7_ выше!

person nilo    schedule 11.02.2020

На эти специальные символы будет ссылаться секция PT_DYNAMIC сгенерированной библиотеки. PT_DYNAMIC определяет различные ресурсы, необходимые для успешного динамического связывания (зависимости библиотек, экспортируемые символы, хеш-таблица символов, массивы init / fini и т. Д.).

Таким образом, любые функции в этих списках будут связаны с разделом PT_DYNAMIC и вызываться в соответствующее время в процессе динамического связывания. Вы можете обратиться к источникам ldd для получения дополнительной информации.

person nneonneo    schedule 07.03.2013
comment
Спасибо за ответ. Я просто погуглил. Раздел .init_array - это специальный раздел, указанный в System V ABI, и я предполагаю, что он автоматически создается gcc. - person Pony279; 07.03.2013

спецификации для этих объектов являются спецификациями для формата файла заголовка elf. хотя бы зачем они там есть.

Они НЕ предназначены для работы в каких-либо формах или формах, если вы не планируете переписывать библиотеку glic и все, с чем она взаимодействует. Короче говоря, для заголовка elf требуется функция _start. Бинарный файл без него не запустится.

Большая часть библиотеки libc написана на ассемблере, а не на C, который не учитывает это. Функция предварительного массива - это способ добавления этого заголовка.

Примеры можно найти в папке gnu-csu в glibc или teeny-efl.git. Он также устанавливает массив как строку с косой чертой. Устанавливает оба элемента как статические, массив в argv и init_array. Позже он проверит, совпадают ли они. Также требуется больше кода, чем вы должны добавить к такой функции, чтобы прервать этот процесс или сделать что-либо, кроме того, для чего он предназначен, что следует оставить в покое. Иди поиграй со своим холодильником.

person Community    schedule 17.01.2018