c linux gcc elf

ejecutando init y fini



linux gcc (2)

Acabo de leer sobre las secciones init y fini en los archivos ELF y lo probé:

#include <stdio.h> int main(){ puts("main"); return 0; } void init(){ puts("init"); } void fini(){ puts("fini"); }

Si hago gcc -Wl,-init,init -Wl,-fini,fini foo.c y ejecuto el resultado, la parte "init" no se imprime:

$ ./a.out main fini

¿No se ejecutó la parte de inicio o no se pudo imprimir de alguna manera?

¿Existe alguna documentación "oficial" sobre el tema init / fini?

man ld dice:

-init=name When creating an ELF executable or shared object, call NAME when the executable or shared object is loaded, by setting DT_INIT to the address of the function. By default, the linker uses "_init" as the function to call.

¿No debería significar eso, que sería suficiente nombrar la función init _init ? (Si hago gcc se queja de la definición múltiple.)


No es un error en ld sino en el código de inicio de glibc para el ejecutable principal. Para los objetos compartidos, se llama a la función establecida por la opción -init .

This es el compromiso de ld agregando las opciones -init y -fini . La función _init del programa no es llamada desde el archivo glibc-2.21/elf/dl-init.c:58 por la entrada DT_INIT por el enlazador dinámico, pero se llama desde __libc_csu_init en el archivo glibc-2.21/csu/elf-init.c:83 por el ejecutable principal.

Es decir, el inicio ignora el puntero a la función en DT_INIT del programa.

Si compilas con -static , fini no se llama, también.

DT_INIT y DT_FINI no deben usarse, porque son de estilo antiguo, vea la línea 255 .

Los siguientes trabajos:

#include <stdio.h> static void preinit(int argc, char **argv, char **envp) { puts(__FUNCTION__); } static void init(int argc, char **argv, char **envp) { puts(__FUNCTION__); } static void fini(void) { puts(__FUNCTION__); } __attribute__((section(".preinit_array"), used)) static typeof(preinit) *preinit_p = preinit; __attribute__((section(".init_array"), used)) static typeof(init) *init_p = init; __attribute__((section(".fini_array"), used)) static typeof(fini) *fini_p = fini; int main(void) { puts(__FUNCTION__); return 0; }

$ gcc -Wall a.c $ ./a.out preinit init main fini $


No hagas eso deje que su compilador y enlazador rellenen las secciones como mejor les parezca.

En su lugar, marque sus funciones con los atributos de función apropiados, de modo que el compilador y el vinculador los coloquen en las secciones correctas.

Por ejemplo,

static void before_main(void) __attribute__((constructor)); static void after_main(void) __attribute__((destructor)); static void before_main(void) { /* This is run before main() */ } static void after_main(void) { /* This is run after main() returns (or exit() is called) */ }

También puede asignar una prioridad (por ejemplo, __attribute__((constructor (300))) ), un entero entre 101 y 65535, inclusive, con las funciones que tienen primero un número de prioridad más pequeño.

Tenga en cuenta que para ilustración, marqué las funciones static . Es decir, las funciones no serán visibles fuera del alcance del archivo. Las funciones no necesitan ser símbolos exportados para ser llamadas automáticamente.

Para las pruebas, sugiero guardar lo siguiente en un archivo separado, digamos tructor.c :

#include <unistd.h> #include <string.h> #include <errno.h> static int outfd = -1; static void wrout(const char *const string) { if (string && *string && outfd != -1) { const char *p = string; const char *const q = string + strlen(string); while (p < q) { ssize_t n = write(outfd, p, (size_t)(q - p)); if (n > (ssize_t)0) p += n; else if (n != (ssize_t)-1 || errno != EINTR) break; } } } void before_main(void) __attribute__((constructor (101))); void before_main(void) { int saved_errno = errno; /* This is run before main() */ outfd = dup(STDERR_FILENO); wrout("Before main()/n"); errno = saved_errno; } static void after_main(void) __attribute__((destructor (65535))); static void after_main(void) { int saved_errno = errno; /* This is run after main() returns (or exit() is called) */ wrout("After main()/n"); errno = saved_errno; }

para que pueda compilarlo y vincularlo como parte de cualquier programa o biblioteca. Para compilarlo como una biblioteca compartida, use por ejemplo

gcc -Wall -Wextra -fPIC -shared tructor.c -Wl,-soname,libtructor.so -o libtructor.so

y puede interponerlo en cualquier comando o binario enlazado dinámicamente

LD_PRELOAD=./libtructor.so some-command-or-binary

Las funciones no cambian errno , aunque no debería importar en la práctica, y usan el sistema de write() bajo nivel para enviar los mensajes a un error estándar. El error estándar inicial se duplica en un nuevo descriptor, porque en muchos casos, el error estándar se cierra antes de que se ejecute el último destructor global, nuestro destructor aquí.

(Algunos binarios paranoicos, generalmente sensibles a la seguridad, cierran todos los descriptores que no conocen, por lo que es posible que no vea el mensaje After main() en todos los casos).