.init
/ .fini
no está en desuso. Todavía es parte del estándar ELF y me atrevería a decir que será para siempre. El código en .init
/ .fini
es ejecutado por el cargador / runtime-linker cuando el código se carga / descarga. Es decir, en cada código ELF de carga (por ejemplo, una biblioteca compartida) .init
se ejecutará. Todavía es posible usar ese mecanismo para lograr casi lo mismo que con __attribute__((constructor))/((destructor))
. Es de la vieja escuela pero tiene algunos beneficios.
.ctors
/ .dtors
mecanismo, por ejemplo, requiere soporte de system-rtl / loader / linker-script. Esto está lejos de ser seguro para estar disponible en todos los sistemas, por ejemplo, sistemas profundamente integrados donde el código se ejecuta en metal desnudo. Es decir, incluso si __attribute__((constructor))/((destructor))
es compatible con GCC, no es seguro que se ejecutará, ya que depende del vinculador organizarlo y del cargador (o, en algunos casos, del código de arranque) ejecutarlo. Para usar .init
/ en su .fini
lugar, la forma más fácil es usar banderas de enlace: -init & -fini (es decir, desde la línea de comandos de GCC, la sintaxis sería -Wl -init my_init -fini my_fini
).
En el sistema que admite ambos métodos, un beneficio posible es que el código .init
se ejecuta antes .ctors
y el código .fini
después .dtors
. Si el orden es relevante, esa es al menos una forma cruda pero fácil de distinguir entre las funciones init / exit.
Una desventaja importante es que no puedes tener más de uno _init
y uno_fini
función por cada módulo cargable y probablemente tendría que fragmentar el código más .so
que motivado. Otra es que cuando se usa el método de enlace descrito anteriormente, uno reemplaza las _fini
funciones _init y predeterminadas originales (proporcionadas por crti.o
). Aquí es donde generalmente se produce todo tipo de inicialización (en Linux aquí es donde se inicializa la asignación de variable global). Aquí se describe una forma de evitarlo
Observe en el enlace anterior que _init()
no se necesita una conexión en cascada con el original, ya que todavía está en su lugar. El call
de la línea de montaje es sin embargo x86-mnemotécnica y llamar a una función del conjunto se vería completamente diferente para muchas otras arquitecturas (como ARM, por ejemplo). Es decir, el código no es transparente.
.init
Los mecanismos / .fini
y .ctors
/ .detors
son similares, pero no del todo. El código en .init
/ se .fini
ejecuta "tal cual". Es decir, puede tener varias funciones en .init
/.fini
, pero AFAIK es sintácticamente difícil ponerlas allí de forma totalmente transparente en C puro sin romper el código en muchos .so
archivos pequeños .
.ctors
/ .dtors
están organizados de manera diferente que .init
/.fini
. .ctors
Las .dtors
secciones / son solo tablas con punteros a funciones, y el "llamador" es un bucle proporcionado por el sistema que llama a cada función indirectamente. Es decir, la llamada de bucle puede ser específica de la arquitectura, pero como es parte del sistema (si es que existe), no importa.
El siguiente fragmento agrega nuevos punteros de función a la .ctors
matriz de funciones, principalmente de la misma manera que lo __attribute__((constructor))
hace (el método puede coexistir con __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;
También se pueden agregar los punteros de función a una sección autoinventada completamente diferente. Un script enlazador modificado y una función adicional que imita el cargador .ctors
/.dtors
tal caso, se necesita bucle. Pero con él se puede lograr un mejor control sobre el orden de ejecución, agregar argumentos y manejo de código de retorno eta (en un proyecto de C ++, por ejemplo, sería útil si necesita algo que se ejecute antes o después de los constructores globales).
Preferiría __attribute__((constructor))/((destructor))
siempre que sea posible, es una solución simple y elegante, incluso se siente como hacer trampa. Para codificadores de metal desnudo como yo, esto no siempre es una opción.
Alguna buena referencia en el libro Linkers & loaders .
#define __attribute__(x)
). Si tiene varios atributos, por ejemplo,__attribute__((noreturn, weak))
sería difícil "eliminar" si solo hubiera un conjunto de paréntesis.