Sie beschreiben dem Loader, wie das ELF in den Speicher geladen werden soll:
readelf-lWlnstatElffiletypeisDYN (Position-Independent Executablefile)Entrypoint0x1c00Thereare9programheaders,startingatoffset64ProgramHeaders:TypeOffsetVirtAddrPhysAddrFileSizMemSizFlgAlignPHDR0x0000400x00000000000000400x00000000000000400x0001f80x0001f8R0x8INTERP0x0002380x00000000000002380x00000000000002380x00001b0x00001bR0x1[Requesting program interpreter: /lib/ld-linux-aarch64.so.1]LOAD0x0000000x00000000000000000x00000000000000000x003f7c0x003f7cRE0x10000LOAD0x00fc480x000000000001fc480x000000000001fc480x0005280x001190RW0x10000DYNAMIC0x00fc580x000000000001fc580x000000000001fc580x0002000x000200RW0x8NOTE0x0002540x00000000000002540x00000000000002540x0000e00x0000e0R0x4GNU_EH_FRAME0x0036100x00000000000036100x00000000000036100x0001b40x0001b4R0x4GNU_STACK0x0000000x00000000000000000x00000000000000000x0000000x000000RW0x10GNU_RELRO0x00fc480x000000000001fc480x000000000001fc480x0003b80x0003b8R0x1SectiontoSegmentmapping:SegmentSections...0001.interp02.interp.note.gnu.build-id.note.ABI-tag.note.package.gnu.hash.dynsym.dynstr.gnu.version.gnu.version_r.rela.dyn.rela.plt.init.plt.text.fini.rodata.eh_frame_hdr.eh_frame03.init_array.fini_array.dynamic.got.data.bss04.dynamic05.note.gnu.build-id.note.ABI-tag.note.package06.eh_frame_hdr0708.init_array.fini_array.dynamic.got
Der vorherige Programm hat 9 Programm-Header, dann zeigt die Segmentzuordnung, in welchem Programm-Header (von 00 bis 08) jeder Abschnitt sich befindet.
PHDR - Programm-Header
Enthält die Programm-Header-Tabellen und die Metadaten selbst.
INTERP
Gibt den Pfad des Loaders an, der verwendet werden soll, um die Binärdatei in den Speicher zu laden.
LOAD
Diese Header werden verwendet, um anzugeben, wie eine Binärdatei in den Speicher geladen werden soll.
Jeder LOAD-Header gibt einen Bereich des Speichers (Größe, Berechtigungen und Ausrichtung) an und zeigt die Bytes der ELF Binärdatei, die dort kopiert werden sollen.
Zum Beispiel hat der zweite eine Größe von 0x1190, sollte sich bei 0x1fc48 mit den Berechtigungen Lesen und Schreiben befinden und wird mit 0x528 ab dem Offset 0xfc48 gefüllt (es füllt nicht den gesamten reservierten Platz). Dieser Speicher wird die Abschnitte .init_array .fini_array .dynamic .got .data .bss enthalten.
DYNAMIC
Dieser Header hilft, Programme mit ihren Bibliotheksabhängigkeiten zu verknüpfen und Relokationen anzuwenden. Überprüfen Sie den .dynamic Abschnitt.
NOTE
Dies speichert Metadateninformationen des Anbieters über die Binärdatei.
GNU_EH_FRAME
Definiert den Standort der Stack-Unwind-Tabellen, die von Debuggern und C++-Ausnahmebehandlungs-Laufzeitfunktionen verwendet werden.
GNU_STACK
Enthält die Konfiguration der Stack-Ausführungspräventionsverteidigung. Wenn aktiviert, kann die Binärdatei keinen Code vom Stack ausführen.
GNU_RELRO
Gibt die RELRO (Relocation Read-Only) Konfiguration der Binärdatei an. Dieser Schutz markiert bestimmte Abschnitte des Speichers (wie die GOT oder die init und fini Tabellen) nach dem Laden des Programms und bevor es zu laufen beginnt, als schreibgeschützt.
Im vorherigen Beispiel werden 0x3b8 Bytes nach 0x1fc48 als schreibgeschützt kopiert, was die Abschnitte .init_array .fini_array .dynamic .got .data .bss betrifft.
Beachten Sie, dass RELRO teilweise oder vollständig sein kann, die partielle Version schützt den Abschnitt .plt.got nicht, der für lazy binding verwendet wird und diesen Speicherplatz benötigt, um Schreibberechtigungen zu haben, um die Adresse der Bibliotheken beim ersten Suchen ihres Standorts zu schreiben.
TLS
Definiert eine Tabelle von TLS-Einträgen, die Informationen über thread-lokale Variablen speichert.
Abschnitts-Header
Abschnitts-Header geben einen detaillierteren Überblick über die ELF-Binärdatei.
Es zeigt auch den Standort, Offset, Berechtigungen, aber auch den Datentyp, den der Abschnitt hat.
Meta-Abschnitte
Stringtabelle: Sie enthält alle Strings, die von der ELF-Datei benötigt werden (aber nicht die, die tatsächlich vom Programm verwendet werden). Zum Beispiel enthält sie Abschnittsnamen wie .text oder .data. Und wenn .text bei Offset 45 in der Stringtabelle ist, wird die Zahl 45 im name-Feld verwendet.
Um zu finden, wo sich die Stringtabelle befindet, enthält die ELF einen Zeiger auf die Stringtabelle.
Symboltabelle: Sie enthält Informationen über die Symbole wie den Namen (Offset in der Stringtabelle), Adresse, Größe und weitere Metadaten über das Symbol.
Hauptabschnitte
.text: Die Anweisungen des Programms, die ausgeführt werden sollen.
.data: Globale Variablen mit einem definierten Wert im Programm.
.bss: Globale Variablen, die nicht initialisiert sind (oder auf null gesetzt). Variablen hier werden automatisch auf null initialisiert, wodurch unnötige Nullen vermieden werden, die zur Binärdatei hinzugefügt werden.
.tdata und .tbss: Wie die .data und .bss, wenn thread-lokale Variablen verwendet werden (__thread_local in C++ oder __thread in C).
.dynamic: Siehe unten.
Symbole
Symbole sind ein benannter Ort im Programm, der eine Funktion, ein globales Datenobjekt, thread-lokale Variablen usw. sein kann.
readelf -s lnstat
Symbol table '.dynsym' contains 49 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000001088 0 SECTION LOCAL DEFAULT 12 .init
2: 0000000000020000 0 SECTION LOCAL DEFAULT 23 .data
3: 0000000000000000 0 FUNC GLOBAL DEFAULT UND strtok@GLIBC_2.17 (2)
4: 0000000000000000 0 FUNC GLOBAL DEFAULT UND s[...]@GLIBC_2.17 (2)
5: 0000000000000000 0 FUNC GLOBAL DEFAULT UND strlen@GLIBC_2.17 (2)
6: 0000000000000000 0 FUNC GLOBAL DEFAULT UND fputs@GLIBC_2.17 (2)
7: 0000000000000000 0 FUNC GLOBAL DEFAULT UND exit@GLIBC_2.17 (2)
8: 0000000000000000 0 FUNC GLOBAL DEFAULT UND _[...]@GLIBC_2.34 (3)
9: 0000000000000000 0 FUNC GLOBAL DEFAULT UND perror@GLIBC_2.17 (2)
10: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_deregisterT[...]
11: 0000000000000000 0 FUNC WEAK DEFAULT UND _[...]@GLIBC_2.17 (2)
12: 0000000000000000 0 FUNC GLOBAL DEFAULT UND putc@GLIBC_2.17 (2)
[...]
Jeder Symbol-Eintrag enthält:
Name
Bindungsattribute (schwach, lokal oder global): Ein lokales Symbol kann nur vom Programm selbst zugegriffen werden, während die globalen Symbole außerhalb des Programms geteilt werden. Ein schwaches Objekt ist zum Beispiel eine Funktion, die von einer anderen überschrieben werden kann.
Typ: NOTYPE (kein Typ angegeben), OBJECT (globale Datenvariable), FUNC (Funktion), SECTION (Sektion), FILE (Quellcode-Datei für Debugger), TLS (thread-lokale Variable), GNU_IFUNC (indirekte Funktion für Relokation)
Das NEEDED-Verzeichnis zeigt an, dass das Programm die erwähnte Bibliothek laden muss, um fortzufahren. Das NEEDED-Verzeichnis wird abgeschlossen, sobald die gemeinsame Bibliothek vollständig betriebsbereit und bereit zur Verwendung ist.
Relokationen
Der Loader muss auch Abhängigkeiten nach dem Laden umsetzen. Diese Relokationen sind in der Relokationstabelle im Format REL oder RELA angegeben, und die Anzahl der Relokationen wird in den dynamischen Abschnitten RELSZ oder RELASZ angegeben.
Wenn das Programm an einem anderen Ort geladen wird als der bevorzugte Adresse (normalerweise 0x400000), weil die Adresse bereits verwendet wird oder wegen ASLR oder aus einem anderen Grund, korrigiert eine statische Relokation Zeiger, die Werte hatten, die erwarteten, dass das Binärprogramm an der bevorzugten Adresse geladen wird.
Zum Beispiel sollte jeder Abschnitt vom Typ R_AARCH64_RELATIV die Adresse am Relokationsbias plus den Addendwert modifizieren.
Dynamische Relokationen und GOT
Die Relokation könnte auch auf ein externes Symbol verweisen (wie eine Funktion aus einer Abhängigkeit). Wie die Funktion malloc aus libC. Dann wird der Loader beim Laden von libC an einer Adresse überprüfen, wo die malloc-Funktion geladen ist, und diese Adresse in die GOT (Global Offset Table) Tabelle (angegeben in der Relokationstabelle) schreiben, wo die Adresse von malloc angegeben werden sollte.
Prozedurenverknüpfungstabelle
Der PLT-Abschnitt ermöglicht eine verzögerte Bindung, was bedeutet, dass die Auflösung des Standorts einer Funktion beim ersten Zugriff durchgeführt wird.
Wenn ein Programm also malloc aufruft, ruft es tatsächlich den entsprechenden Standort von malloc im PLT (malloc@plt) auf. Beim ersten Aufruf wird die Adresse von malloc aufgelöst und gespeichert, sodass beim nächsten Aufruf von malloc diese Adresse anstelle des PLT-Codes verwendet wird.
Programminitialisierung
Nachdem das Programm geladen wurde, ist es Zeit, dass es ausgeführt wird. Der erste Code, der ausgeführt wird, ist jedoch nicht immer die main-Funktion. Dies liegt daran, dass zum Beispiel in C++, wenn eine globale Variable ein Objekt einer Klasse ist, dieses Objekt initialisiertwerden muss, bevor main ausgeführt wird, wie in:
Beachten Sie, dass sich diese globalen Variablen in .data oder .bss befinden, aber in den Listen __CTOR_LIST__ und __DTOR_LIST__ die Objekte zur Initialisierung und Zerstörung gespeichert sind, um den Überblick über sie zu behalten.
Aus C-Code ist es möglich, dasselbe Ergebnis mit den GNU-Erweiterungen zu erzielen:
__attributte__((constructor)) //Add a constructor to execute before__attributte__((destructor)) //Add to the destructor list
From a compiler perspective, um diese Aktionen vor und nach der Ausführung der main-Funktion auszuführen, ist es möglich, eine init-Funktion und eine fini-Funktion zu erstellen, die im dynamischen Abschnitt als INIT und FIN referenziert werden. Sie werden in den init- und fini-Abschnitten des ELF platziert.
Die andere Option, wie erwähnt, besteht darin, die Listen __CTOR_LIST__ und __DTOR_LIST__ in den INIT_ARRAY- und FINI_ARRAY-Einträgen im dynamischen Abschnitt zu referenzieren, deren Länge durch INIT_ARRAYSZ und FINI_ARRAYSZ angegeben wird. Jeder Eintrag ist ein Funktionszeiger, der ohne Argumente aufgerufen wird.
Darüber hinaus ist es auch möglich, ein PREINIT_ARRAY mit Zeigern zu haben, die vor den INIT_ARRAY-Zeigern ausgeführt werden.
Initialisierungsreihenfolge
Das Programm wird in den Speicher geladen, statische globale Variablen werden in .data initialisiert und nicht initialisierte werden in .bss auf Null gesetzt.
Alle Abhängigkeiten für das Programm oder Bibliotheken werden initialisiert und das dynamische Linking wird ausgeführt.
PREINIT_ARRAY-Funktionen werden ausgeführt.
INIT_ARRAY-Funktionen werden ausgeführt.
Wenn es einen INIT-Eintrag gibt, wird dieser aufgerufen.
Wenn eine Bibliothek, endet dlopen hier, wenn ein Programm, ist es Zeit, den echten Einstiegspunkt (Funktion main) aufzurufen.
Thread-Local Storage (TLS)
Sie werden mit dem Schlüsselwort __thread_local in C++ oder der GNU-Erweiterung __thread definiert.
Jeder Thread wird einen einzigartigen Speicherort für diese Variable beibehalten, sodass nur der Thread auf seine Variable zugreifen kann.
Wenn dies verwendet wird, werden die Abschnitte .tdata und .tbss im ELF verwendet. Diese sind wie .data (initialisiert) und .bss (nicht initialisiert), aber für TLS.
Jede Variable hat einen Eintrag im TLS-Header, der die Größe und den TLS-Offset angibt, der der Offset ist, den sie im lokalen Datenbereich des Threads verwenden wird.
Der __TLS_MODULE_BASE ist ein Symbol, das verwendet wird, um auf die Basisadresse des Thread-Local Storage zu verweisen und auf den Bereich im Speicher zeigt, der alle thread-lokalen Daten eines Moduls enthält.