.init
/ .fini
n'est pas déprécié. Il fait toujours partie du standard ELF et j'ose dire qu'il le sera toujours. Code en .init
/ .fini
est exécuté par le loader/runtime-linker lorsque le code est chargé/déchargé. C'est à dire qu'à chaque chargement ELF (par exemple une bibliothèque partagée) le code dans le fichier .init
sera exécuté. Il est toujours possible d'utiliser ce mécanisme pour réaliser à peu près la même chose qu'avec __attribute__((constructor))/((destructor))
. C'est de la vieille école mais ça a des avantages.
.ctors
/ .dtors
par exemple nécessitent un support par system-rtl/loader/linker-script. Il est loin d'être certain que cela soit disponible sur tous les systèmes, par exemple les systèmes profondément embarqués où le code s'exécute sur le métal nu. C'est-à-dire que même si __attribute__((constructor))/((destructor))
est supporté par GCC, il n'est pas certain qu'il fonctionnera car c'est au linker de l'organiser et au loader (ou dans certains cas, au boot-code) de l'exécuter. Pour utiliser .init
/ .fini
à la place, le moyen le plus simple est d'utiliser les drapeaux de liaison : -init & -fini (i.e. à partir de la ligne de commande de GCC, la syntaxe serait la suivante -Wl -init my_init -fini my_fini
).
Sur les systèmes supportant les deux méthodes, un avantage possible est que le code en .init
est exécuté avant .ctors
et le code dans .fini
après .dtors
. Si l'ordre est pertinent, c'est au moins un moyen rudimentaire mais facile de distinguer les fonctions init/exit.
L'inconvénient majeur est qu'il n'est pas facile d'en avoir plusieurs. _init
et un _fini
pour chaque module chargeable et il faudrait probablement fragmenter le code en plusieurs fonctions. .so
que motivé. Une autre raison est que lorsque l'on utilise la méthode de liaison décrite ci-dessus, on remplace les fichiers originaux _init et _fini
les fonctions par défaut (fournies par crti.o
). C'est là que toutes sortes d'initialisations se produisent habituellement (sous Linux, c'est là que l'affectation des variables globales est initialisée). Une façon de contourner cela est décrite ici
Remarquez dans le lien ci-dessus qu'une cascade vers l'original _init()
n'est pas nécessaire puisqu'il est toujours en place. Le site call
dans l'assemblage en ligne est cependant mnémotechnique x86 et l'appel d'une fonction à partir de l'assemblage serait complètement différent pour de nombreuses autres architectures (comme ARM par exemple). En d'autres termes, le code n'est pas transparent.
.init
/ .fini
et .ctors
/ .detors
Les mécanismes sont similaires, mais pas tout à fait. Le code en .init
/ .fini
fonctionne "tel quel". C'est-à-dire que vous pouvez avoir plusieurs fonctions dans le fichier .init
/ .fini
mais il est difficile, d'un point de vue syntaxique, de les intégrer de manière totalement transparente en C pur sans casser le code en de nombreux petits morceaux. .so
des fichiers.
.ctors
/ .dtors
sont organisées différemment que .init
/ .fini
. .ctors
/ .dtors
sont toutes deux de simples tableaux contenant des pointeurs vers des fonctions, et l'"appelant" est une boucle fournie par le système qui appelle chaque fonction indirectement. En d'autres termes, l'appelant de la boucle peut être spécifique à l'architecture, mais comme il fait partie du système (si tant est qu'il existe), cela n'a pas d'importance.
L'extrait suivant ajoute de nouveaux pointeurs de fonction à l'objet .ctors
principalement de la même manière que la fonction __attribute__((constructor))
fait (la méthode peut coexister avec __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;
On peut également ajouter les pointeurs de fonction à une section complètement différente inventée par soi-même. Un linker modifié script et une fonction supplémentaire imitant le loader. .ctors
/ .dtors
Une boucle est nécessaire dans ce cas. Mais avec elle, on peut obtenir un meilleur contrôle de l'ordre d'exécution, ajouter la gestion du code in-argument et du code de retour, etc. (dans un projet C++, par exemple, elle serait utile si l'on avait besoin d'exécuter quelque chose avant ou après les constructeurs globaux).
Je préférerais __attribute__((constructor))/((destructor))
lorsque cela est possible, c'est une solution simple et élégante même si elle donne l'impression de tricher. Pour les codeurs "bare-metal" comme moi, ce n'est tout simplement pas toujours une option.
Quelques bonnes références dans le livre Liaisons et chargeurs .