.init
/ .fini
n'est pas déconseillé. Cela fait toujours partie de la norme ELF et j'oserais dire que ce sera pour toujours. Le code dans .init
/ .fini
est exécuté par le chargeur / runtime-linker lorsque le code est chargé / déchargé. C'est-à-dire sur chaque chargement ELF (par exemple une bibliothèque partagée) le code .init
sera exécuté. Il est toujours possible d'utiliser ce mécanisme pour obtenir à peu près la même chose qu'avec __attribute__((constructor))/((destructor))
. C'est de la vieille école, mais cela présente certains avantages.
.ctors
/ Le .dtors
mécanisme nécessite par exemple le support de system-rtl / loader / linker-script. Ceci est loin d'être certain d'être disponible sur tous les systèmes, par exemple les systèmes profondément embarqués où le code s'exécute sur du métal nu. C'est-à-dire que même s'il __attribute__((constructor))/((destructor))
est pris en charge par GCC, il n'est pas certain qu'il fonctionnera car c'est à l'éditeur de liens de l'organiser et au chargeur (ou, dans certains cas, au code de démarrage) de l'exécuter. Pour utiliser .init
/ à la .fini
place, la manière la plus simple consiste à utiliser des indicateurs de l'éditeur de liens: -init & -fini (c'est-à-dire à partir de la ligne de commande GCC, la syntaxe serait -Wl -init my_init -fini my_fini
).
Sur un système prenant en charge les deux méthodes, un avantage possible est que le code .init
entrant est exécuté avant .ctors
et le code entré .fini
après .dtors
. Si l'ordre est pertinent, c'est au moins un moyen simple mais simple de distinguer les fonctions init / exit.
Un inconvénient majeur est que vous ne pouvez pas facilement avoir plus d'une _init
et une _fini
fonction par module chargeable et que vous devrez probablement fragmenter le code plus .so
que motivé. Un autre est que lorsque vous utilisez la méthode de l'éditeur de liens décrite ci-dessus, on remplace les fonctions d'origine _init et _fini
par défaut (fournies par crti.o
). C'est là que se produisent généralement toutes sortes d'initialisations (sous Linux, c'est là que l'affectation des variables globales est initialisée). Un moyen de contourner cela est décrit ici
Notez dans le lien ci-dessus qu'une cascade vers l'original _init()
n'est pas nécessaire car elle est toujours en place. Le call
dans l'assemblage en ligne est cependant x86 mnémoniques et appeler une fonction de montage serait complètement différent pour beaucoup d' autres architectures (comme ARM par exemple). C'est-à-dire que le code n'est pas transparent.
.init
Les mécanismes / .fini
et .ctors
/ .detors
sont similaires, mais pas tout à fait. Le code dans .init
/ .fini
s'exécute "tel quel". C'est-à-dire que vous pouvez avoir plusieurs fonctions dans .init
/ .fini
, mais il est AFAIK syntaxiquement difficile de les mettre de manière totalement transparente en C pur sans casser le code dans de nombreux petits .so
fichiers.
.ctors
/ .dtors
sont organisés différemment de .init
/ .fini
. .ctors
Les .dtors
sections / ne sont que des tables avec des pointeurs vers des fonctions, et "l'appelant" est une boucle fournie par le système qui appelle chaque fonction indirectement. C'est-à-dire que l'appelant en boucle peut être spécifique à l'architecture, mais comme il fait partie du système (s'il existe), cela n'a pas d'importance.
L'extrait suivant ajoute de nouveaux pointeurs de .ctors
fonction au tableau de fonctions, principalement de la même manière que __attribute__((constructor))
(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 auto-inventée complètement différente. Un script de l'éditeur de liens modifié et une fonction supplémentaire imitant le chargeur .ctors
/ la .dtors
boucle sont nécessaires dans ce cas. Mais avec cela, on peut obtenir un meilleur contrôle sur l'ordre d'exécution, ajouter des arguments et renvoyer le code de gestion eta (dans un projet C ++ par exemple, il serait utile s'il a besoin de quelque chose en cours d'exécution avant ou après les constructeurs globaux).
Je préfère __attribute__((constructor))/((destructor))
autant que possible, c'est une solution simple et élégante même si on a l'impression de tricher. Pour les codeurs nus comme moi, ce n'est tout simplement pas toujours une option.
Quelques bonnes références dans le livre Linkers & loaders .
#define __attribute__(x)
). Si vous avez plusieurs attributs, par exemple,__attribute__((noreturn, weak))
il serait difficile de "macro-out" s'il n'y avait qu'un seul ensemble de crochets.