.init
/ .fini
не застаріло. Це все ще є частиною стандарту ELF, і я наважусь сказати, що це буде назавжди. Код в .init
/ .fini
запускається завантажувачем / програмою-лінкером, коли код завантажується / вивантажується. Тобто на кожному завантаженні ELF (наприклад, спільна бібліотека) .init
буде запущений код у . Ще можна використовувати цей механізм для досягнення приблизно того ж, що і з __attribute__((constructor))/((destructor))
. Це стара школа, але вона має деякі переваги.
.ctors
/ .dtors
механізм, наприклад, вимагає підтримки system-rtl / loader / linker-script. Це далеко не напевно доступне для всіх систем, наприклад, глибоко вбудованих систем, де код виконується на голому металі. Тобто, навіть якщо __attribute__((constructor))/((destructor))
він підтримується GCC, невідомо, що він запуститься, оскільки це залежить від того, що він може його організувати, і завантажувач (або в деяких випадках завантажувальний код) для його запуску. Для використання .init
/ .fini
натомість найпростішим способом є використання прапорів-лінкерів: -init & -fini (тобто з командного рядка GCC, це був би синтаксис -Wl -init my_init -fini my_fini
).
У системі, що підтримує обидва методи, однією з можливих переваг є те, що код у .init
запускається до, .ctors
а код - .fini
після .dtors
. Якщо замовлення доречне, це хоча б один неочищений, але простий спосіб розрізнити функції init / exit.
Основним недоліком є те, що ви не можете легко мати більше однієї _init
та однієї _fini
функції на кожен завантажуваний модуль, і, ймовірно, доведеться фрагментувати код більш .so
ніж мотивованим. Інша полягає в тому, що, використовуючи описаний вище метод лінкера, один замінює оригінальні функції _init та _fini
за замовчуванням (надаються crti.o
). Тут зазвичай відбуваються всілякі ініціалізації (в Linux це ініціалізація глобальних змінних). Шлях, описаний тут, описаний
Зауважте за посиланням вище, що каскадування оригіналу _init()
не потрібно, оскільки воно все ще існує. Однак call
вбудована збірка є x86-мнемонічною, а виклик функції з складання виглядав би зовсім інакше для багатьох інших архітектур (наприклад, ARM). Тобто код не прозорий.
.init
/ .fini
та .ctors
/ .detors
механізми схожі, але не зовсім. Код в .init
/ .fini
працює "як є". Тобто ви можете мати кілька функцій в .init
/ .fini
, але синтаксично важко розмістити їх там, у чистому С, без розбиття коду у багатьох невеликих .so
файлах.
.ctors
/ .dtors
організовані інакше, ніж .init
/ .fini
. .ctors
/ .dtors
розділи - це просто таблиці з покажчиками на функції, а "абонент" - це цикл, що надається системою, який викликає кожну функцію опосередковано. Тобто виклик циклу може бути специфічним для архітектури, але оскільки він є частиною системи (якщо вона взагалі існує, тобто), це не має значення.
Наступний фрагмент додає нові функції покажчиків до .ctors
масиву функцій, головним чином так само, як і __attribute__((constructor))
це (метод може співіснувати з __attribute__((constructor)))
.
#define SECTION( S ) __attribute__ ((section ( S )))
void test(void) {
printf("Hello\n");
}
void (*funcptr)(void) SECTION(".ctors") =test;
void (*funcptr2)(void) SECTION(".ctors") =test;
void (*funcptr3)(void) SECTION(".dtors") =test;
Також можна додати покажчики функцій до зовсім іншого самостійно винайденого розділу. У такому випадку потрібен модифікований сценарій зв’язку та додаткова функція, що імітує завантажувач .ctors
/ .dtors
цикл. Але за допомогою цього можна досягти кращого контролю над порядком виконання, додавати вхідний аргумент і повертати код обробки етапу (наприклад, у проекті C ++, було б корисно, якщо вам потрібно щось, що працює до або після глобальних конструкторів).
Я вважаю за краще, __attribute__((constructor))/((destructor))
де це можливо, це просте і елегантне рішення, навіть таке відчуття, як обман. Для гофрованих металевих кодерів, як я, це просто не завжди варіант.
Кілька хороших посилань у книзі Повізники та навантажувачі .
#define __attribute__(x)
). Якщо у вас є кілька атрибутів, наприклад,__attribute__((noreturn, weak))
важко буде "макрос", якби був лише один набір дужок.