Was bedeutet @plt hier?

76
0x00000000004004b6 <main+30>:   callq  0x400398 <printf@plt>

Weiß jemand Bescheid?

AKTUALISIEREN

Warum disas printfgeben mir zwei unterschiedliche Ergebnisse?

(gdb) disas printf
Dump of assembler code for function printf@plt:
0x0000000000400398 <printf@plt+0>:  jmpq   *0x2004c2(%rip)        # 0x600860 <_GLOBAL_OFFSET_TABLE_+24>
0x000000000040039e <printf@plt+6>:  pushq  $0x0
0x00000000004003a3 <printf@plt+11>: jmpq   0x400388

(gdb) disas printf
Dump of assembler code for function printf:
0x00000037aa44d360 <printf+0>:  sub    $0xd8,%rsp
0x00000037aa44d367 <printf+7>:  mov    %rdx,0x30(%rsp)
0x00000037aa44d36c <printf+12>: movzbl %al,%edx
0x00000037aa44d36f <printf+15>: mov    %rsi,0x28(%rsp)
0x00000037aa44d374 <printf+20>: lea    0x0(,%rdx,4),%rax
0x00000037aa44d37c <printf+28>: lea    0x3f(%rip),%rdx        # 0x37aa44d3c2 <printf+98>
gdb
quelle
Woher kam diese erste Ausgangsleitung? objdumpIch stelle mir vor?
Ciro Santilli 31 冠状 病 六四 事件 31

Antworten:

121

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@pltist eigentlich ein kleiner Stub, der (irgendwann) die eigentliche printfFunktion 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 PLTum 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, ProcAund 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@pltden 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- printfund nicht an den PLT-Setup-Code gerichtet werden.
  • Anschließend wird der geladene printfCode 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@pltden 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 glibczur Laufzeit geladen wird.

paxdiablo
quelle
1
Warum disas printfgeben mir zwei unterschiedliche Ergebnisse?
GDB
2
Der Stub ist, bevor ich rbreak
gdb
Es kann mit jeder ausführbaren Datei mit gdb reproduziert werden.
GDB
3
Es ist die Prozedurverknüpfungstabelle. Ich werde mit einigen weiteren Informationen aktualisieren.
Paxdiablo
1
@ Luis, ich bin mir nicht sicher, ob es das gibt. Ich denke, der Setup-Code lädt die gemeinsam genutzte Bibliothek tatsächlich als Teil ihres Prozesses (und ASLR bedeutet, dass sie an einer beliebigen Adresse geladen wird), sodass die Adresse erst nach diesem Zeitpunkt bekannt wird.
Paxdiablo
6

Ich bin mir nicht sicher, aber wahrscheinlich macht das, was Sie gesehen haben, Sinn. Wenn Sie den Befehl disas zum ersten Mal ausführen, wird printf noch nicht aufgerufen, sodass es nicht aufgelöst wird. Sobald Ihr Programm die printf-Methode aufruft, wenn das GOT zum ersten Mal aktualisiert wird, wird das printf aufgelöst und das GOT zeigt auf die eigentliche Funktion. Daher zeigt der nächste Aufruf des Befehls disas die tatsächliche printf-Assembly.

rkachach
quelle