Auf diese Weise können Codekorrekturen durchgeführt werden (Anpassen von Adressen basierend auf dem Speicherort des Codes im virtuellen Speicher, die je nach Prozess unterschiedlich sein können), ohne dass für jeden Prozess eine separate Kopie des Codes verwaltet werden muss. Das PLT ist die Prozedurverknüpfungstabelle , eine der Strukturen, die das dynamische Laden und Verknüpfen einfacher macht.
printf@plt
ist eigentlich ein kleiner Stub, der (irgendwann) die eigentliche printf
Funktion aufruft und Dinge auf dem Weg modifiziert, um nachfolgende Aufrufe schneller zu machen.
Die reale printf
Funktion kann an einer beliebigen Stelle in einem bestimmten Prozess (virtueller Adressraum) abgebildet werden, ebenso wie der Code, der versucht, sie aufzurufen.
Um eine ordnungsgemäße gemeinsame Nutzung von Code für aufrufenden Code (linke Seite unten) und aufgerufenen Code (rechte Seite unten) zu ermöglichen, möchten Sie keine Korrekturen direkt auf den aufrufenden Code anwenden, da dies die Position des aufrufenden Codes einschränkt andere Prozesse.
Es handelt sich also PLT
um einen kleineren prozessspezifischen Bereich mit einer zur Laufzeit zuverlässig berechneten Adresse, der nicht von Prozessen gemeinsam genutzt wird. Daher kann jeder Prozess ihn beliebig ändern, ohne dass dies nachteilige Auswirkungen hat.
Untersuchen Sie das folgende Diagramm, das sowohl Ihren Code als auch den Bibliothekscode zeigt, der verschiedenen virtuellen Adressen in zwei verschiedenen Prozessen zugeordnet ist, ProcA
und ProcB
:
Address: 0x1234 0x9000 0x8888
+-------------+ +---------+ +---------+
| | | Private | | |
ProcA | | | PLT/GOT | | |
| Shared | +---------+ | Shared |
========| application |=============| library |==
| code | +---------+ | code |
| | | Private | | |
ProcB | | | PLT/GOT | | |
+-------------+ +---------+ +---------+
Address: 0x2020 0x9000 0x6666
Dieses spezielle Beispiel zeigt einen einfachen Fall, in dem das PLT einem festen Ort zugeordnet ist. In Ihrem Szenario befindet es sich relativ zum aktuellen Programmzähler, wie aus Ihrer relativen Suche nach Programmzählern hervorgeht:
<printf@plt+0>: jmpq *0x2004c2(%rip) ; 0x600860 <_GOT_+24>
Ich habe gerade eine feste Adressierung verwendet, um das Beispiel einfacher zu halten.
Die ursprüngliche Art und Weise , in dem Code geteilt wurde gemeint , sie hatten an dem zu ladende gleicht jeden Prozess in jedem virtuellen Adressraum Speicherplatz , die es verwendete. Entweder das oder es konnte nicht freigegeben werden, da das Korrigieren der einzelnen freigegebenen Kopie für einen Prozess andere Prozesse, bei denen sie einem anderen Speicherort zugeordnet wurden, vollständig überfüllte.
Durch die Verwendung von positionsunabhängigem Code zusammen mit dem PLT und einer globalen Offset-Tabelle (GOT) ist der erste Aufruf einer Funktion printf@plt
(im PLT) eine mehrstufige Operation, in der die folgenden Aktionen ausgeführt werden:
- Sie rufen
printf@plt
den PLT an.
- Es ruft die GOT-Version (über einen Zeiger) auf, die zunächst auf einen Setup-Code im PLT verweist.
- Dieser Setup-Code lädt die entsprechende gemeinsam genutzte Bibliothek, falls dies noch nicht geschehen ist, und ändert dann den GOT-Zeiger so, dass nachfolgende Aufrufe direkt an den Real-
printf
und nicht an den PLT-Setup-Code gerichtet werden.
- Anschließend wird der geladene
printf
Code an der richtigen Adresse für diesen Vorgang aufgerufen.
Bei nachfolgenden Aufrufen wird der mehrstufige Ansatz vereinfacht, da der GOT-Zeiger geändert wurde:
- Sie rufen
printf@plt
den PLT an.
- Es ruft die GOT-Version (über den Zeiger) auf, die jetzt auf die reale zeigt
printf
.
Ein guter Artikel kann gefunden werden hier , die beschreibt , wie glibc
zur Laufzeit geladen wird.
objdump
Ich stelle mir vor?