Ausführen von init und fini

Lesezeit: 7 Minuten

Benutzer-Avatar
Michael

Ich habe gerade darüber gelesen init- und fini-Abschnitte in ELF-Dateien und probierte es aus:

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

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

Wenn ich mache gcc -Wl,-init,init -Wl,-fini,fini foo.c und führen Sie das Ergebnis aus, der Teil “init” wird nicht gedruckt:

$ ./a.out
main
fini

Ist der Init-Teil nicht gelaufen oder konnte er irgendwie nicht drucken?

Gibt es eine “offizielle” Dokumentation über das Init/Fini-Zeug?

man ld sagt:

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

Sollte das nicht bedeuten, dass es ausreichen würde, die Init-Funktion zu benennen? _init? (Wenn ich das mache, beschwert sich gcc über mehrere Definitionen.)

  • Das ist seltsam … zu Ihrer ersten Frage – meine GDB zeigt, dass die Init-Funktion überhaupt nicht ausgeführt wird.

    – MByD

    21. September 2015 um 17:06 Uhr

  • Es ist durchaus möglich, dass der Unit-Abschnitt ausgeführt wird, bevor die Standardbibliothek ausreichend initialisiert ist, damit sie funktioniert. Warum versuchen Sie nicht etwas ohne Abhängigkeiten, wie das Setzen einer globalen Variablen, um zu sehen, ob die Unit-Funktion tatsächlich ausgeführt wird.

    – niemand

    21. September 2015 um 17:22 Uhr

  • So wie Sie es getan haben, funktioniert es für die Share-Bibliothek, nicht für Programme, die bereits ein Standard-_init und ein Standard-_fini erhalten.

    – Hibou57

    3. Juni 2020 um 9:08 Uhr

Benutzer-Avatar
Nominelles Tier

Tun Sie das nicht; Lassen Sie Ihren Compiler und Linker die Abschnitte nach Belieben ausfüllen.

Markieren Sie stattdessen Ihre Funktionen mit dem entsprechenden Funktionsattributedamit der Compiler und der Linker sie in die richtigen Abschnitte einfügen.

Zum Beispiel,

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

Sie können auch eine Priorität zuweisen (z. __attribute__((constructor (300)))), eine Ganzzahl zwischen 101 und 65535, einschließlich, wobei Funktionen mit einer kleineren Prioritätsnummer zuerst ausgeführt werden.

Beachten Sie, dass ich zur Veranschaulichung die Funktionen markiert habe static. Das heißt, die Funktionen sind außerhalb des Dateibereichs nicht sichtbar. Die Funktionen müssen keine exportierten Symbole sein, um automatisch aufgerufen zu werden.


Zum Testen schlage ich vor, Folgendes in einer separaten Datei zu speichern, sagen wir 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;
}

Sie können es also als Teil eines beliebigen Programms oder einer beliebigen Bibliothek kompilieren und verknüpfen. Um es als gemeinsam genutzte Bibliothek zu kompilieren, verwenden Sie zB

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

und Sie können es in jeden dynamisch verknüpften Befehl oder jede binäre Verwendung einfügen

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

Die Funktionen bleiben erhalten errno unverändert, obwohl es in der Praxis keine Rolle spielen sollte, und verwenden Sie das niedrige Niveau write() syscall, um die Meldungen an den Standardfehler auszugeben. Der anfängliche Standardfehler wird in einen neuen Deskriptor dupliziert, weil in vielen Fällen der Standardfehler selbst geschlossen wird, bevor der letzte globale Destruktor – hier unser Destruktor – ausgeführt wird.

(Einige paranoide Binärdateien, typischerweise sicherheitssensible, schließen alle Deskriptoren, von denen sie nichts wissen, also Sie könnte nicht sehen After main() Nachricht in allen Fällen.)

  • Wie genau funktionieren diese Funktionsattribute? Generiert der Compiler individuellen Code für die Abschnitte init/fini? Aber soweit ich verstanden habe, ist der Code aus einer anderen Datei (crti.o) verlinkt.

    – Michael

    21. September 2015 um 17:48 Uhr

  • @michas Grundsätzlich stellen diese Attribute in der C-Sprache genau den gleichen Mechanismus zur Verfügung, mit dem globale C++-Objekte zur Initialisierungszeit erstellt werden können. Sowohl C++-Konstruktoren als auch __attribute__((constructor)) -kommentierte C-Funktionen sind einfach Code, der “before main“, damit (im ersteren Fall) die Objekte bis dahin einsatzbereit sind main beginnt.

    – Ich werde nicht existieren Ich werde nicht existieren

    21. September 2015 um 18:31 Uhr


  • @michas: Ja, crti.o stellt den Code bereit, den die GNU-C-Bibliothek im ELF benötigt _init und _fini Abschnitte. Es hängt von der spezifischen verwendeten ABI (x86, x86-64, ARM-Varianten usw.) ab, wie genau die markierten Funktionen aufgerufen werden. Auf x86-64 zum Beispiel glaube ich, dass die Funktionsadressen in der aufgelistet sind .init_array und .fini_array Abschnitte statt (__frame_dummy_init_array_entry bzw. ` __do_global_dtors_aux_fini_array_entry` Symbole).

    – Nominelles Tier

    21. September 2015 um 18:49 Uhr

  • @NominalAnimal – Ich stimme zu, dass Konstruktor-/Destruktorattribute verwendet werden sollten, aber können Sie erklären, warum -init nicht funktioniert hat? (nur um meine neugierde zu befriedigen… 🙂 )

    – MByD

    21. September 2015 um 20:32 Uhr

  • @ MByD: Verwenden readelf -d binary um die dynamischen Symbole in einer Binärdatei aufzulisten. Wenn Linker-Option -init tat alles, würden Sie die Adresse für die sehen INIT Symbol in der obigen Liste unterscheiden sich (zeigen Sie auf das benannte Symbol anstelle der C-Bibliothek _init Funktion). Auf x86-64 ist dies bei den oben genannten Versionen nicht der Fall. ld Möglichkeit -fini ändert die Adresse des dynamischen Symbols FINI. Dies bedeutet, dass die _fini() aus crti.o wird durch Ihre eigene Funktion ersetzt. (Was auf x86-64 in Ordnung ist, weil _fini() tut nicht wirklich etwas auf dieser Architektur.)

    – Nominelles Tier

    21. September 2015 um 21:49 Uhr

Benutzer-Avatar
4566976

Es ist kein Bug-in ld aber im Glibc-Startcode für die ausführbare Hauptdatei. Bei gemeinsam genutzten Objekten wird die von der festgelegte Funktion verwendet -init Option aufgerufen wird.


Dies ist das Bekenntnis zu ld Hinzufügen der Optionen -init und -fini.


Das _init Funktion des Programms wird nicht aus der Datei aufgerufen glibc-2.21/elf/dl-init.c:58 bis zum DT_INIT Eintrag durch den dynamischen Linker, aber aufgerufen von __libc_csu_init im Ordner glibc-2.21/csu/elf-init.c:83 durch die ausführbare Hauptdatei.

Das heißt, der Funktionszeiger in DT_INIT des Programms wird vom Start ignoriert.

Wenn Sie mit kompilieren -static, fini wird auch nicht gerufen.

DT_INIT und DT_FINI sollten definitiv nicht verwendet werden, weil sie es sind im alten Stil, siehe Zeile 255.

Folgende Arbeiten:

#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
$ 

  • In diesem Fall wäre es ein Fehler entweder in der Manpage oder in ld. Oder gibt es einen guten Grund, es in diesem Fall zu ignorieren?

    – Michael

    21. September 2015 um 18:00 Uhr

  • @ 4566976 – irgendwelche Unterlagen dafür?

    – MByD

    21. September 2015 um 20:32 Uhr

  • ld beschwert sich nicht über einen unbekannten fini-Namen, aber fini funktioniert trotzdem.

    – Michael

    22. September 2015 um 14:51 Uhr

  • Der init-Abschnitt ist Lauf! Sie können durch überprüfen ein bisschen Assembler hinzufügen.

    – Michael

    24. September 2015 um 12:44 Uhr

  • @michas Ja, aber der Funktionszeiger hinein DT_INIT der ausführbaren elf-Datei wird vom dynamischen Linker und der Clib-Initialisierung ignoriert.

    – 4566976

    24. September 2015 um 15:17 Uhr


1367170cookie-checkAusführen von init und fini

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy