web-dev-qa-db-fra.com

exécution d'init et de fini

Je viens de lire sections init et fini dans les fichiers ELF et je l'ai essayé:

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

void init(){
  puts("init");
}
void fini(){
  puts("fini");
}

Si je fais gcc -Wl,-init,init -Wl,-fini,fini foo.c et exécutez le résultat, la partie "init" n'est pas imprimée:

$ ./a.out
main
fini

La partie init ne s'est-elle pas exécutée ou n'a-t-elle pas pu imprimer d'une manière ou d'une autre?

Existe-t-il une documentation "officielle" sur les trucs init/fini?

man ld dit:

 -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.

Cela ne devrait-il pas signifier qu'il suffirait de nommer la fonction init _init? (Si je fais gcc se plaint de plusieurs définitions.)

21
michas

Ne fais pas ça; laissez votre compilateur et votre éditeur de liens remplir les sections comme bon leur semble.

Au lieu de cela, marquez vos fonctions avec les attributs - fonction appropriés , de sorte que le compilateur et l'éditeur de liens les placent dans les sections appropriées.

Par exemple,

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) */
}

Vous pouvez également attribuer une priorité (disons, __attribute__((constructor (300)))), un entier compris entre 101 et 65535 inclusivement, les fonctions ayant un numéro de priorité plus petit étant exécutées en premier.

Notez qu'à titre d'illustration, j'ai marqué les fonctions static. Autrement dit, les fonctions ne seront pas visibles en dehors de la portée du fichier. Les fonctions n'ont pas besoin d'être exportées pour être appelées automatiquement.


Pour les tests, je suggère d'enregistrer les éléments suivants dans un fichier séparé, par exemple 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;
}

afin que vous puissiez le compiler et le lier dans le cadre de n'importe quel programme ou bibliothèque. Pour le compiler en tant que bibliothèque partagée, utilisez par exemple.

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

et vous pouvez l'interposer dans n'importe quelle commande ou binaire lié dynamiquement en utilisant

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

Les fonctions gardent errno inchangées, bien que cela ne devrait pas avoir d'importance dans la pratique, et utilisent le syscall write() de bas niveau pour sortir les messages en erreur standard. L'erreur standard initiale est dupliquée dans un nouveau descripteur, car dans de nombreux cas, l'erreur standard elle-même est fermée avant l'exécution du dernier destructeur global - notre destructeur ici -.

(Certains fichiers binaires paranoïdes, généralement sensibles à la sécurité, ferment tous les descripteurs qu'ils ne connaissent pas, donc vous pourriez ne pas voir la After main() message dans tous les cas.)

13
Nominal Animal