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