web-dev-qa-db-fra.com

Que signifie @plt ici?

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

Quelqu'un sait?

MISE À JOUR

Pourquoi deux disas printf me donne un résultat différent?

(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>
69
gdb

C'est un moyen d'obtenir des corrections de code (ajustement des adresses en fonction de l'emplacement du code dans la mémoire virtuelle, qui peut être différent d'un processus à l'autre) sans avoir à conserver une copie distincte du code pour chaque processus. Le PLT est la table de liaison des procédures, l'une des structures qui facilite le chargement et la liaison dynamiques.

printf@plt est en fait un petit bout qui (éventuellement) appelle la vraie fonction printf, modifiant les choses sur le chemin pour rendre les appels suivants plus rapides.

La fonction réelle printf peut être mappée dans n'importe laquelle l'emplacement dans un processus donné (espace d'adressage virtuel), tout comme le code qui essaie de l'appeler.

Ainsi, afin de permettre un partage de code correct du code appelant (côté gauche ci-dessous) et du code appelé (côté droit ci-dessous), vous ne voulez pas appliquer de correctifs directement au code appelant car cela restreindra où il peut être situé dans autres processus.

Ainsi, PLT est une zone plus petite spécifique au processus à une adresse calculée de manière fiable lors de l'exécution qui n'est pas partagé entre les processus, donc tout processus donné est libre de le changer comme bon lui semble, sans effets indésirables.


Examinez le diagramme suivant qui montre à la fois votre code et le code de bibliothèque mappé à différentes adresses virtuelles dans deux processus différents, ProcA et ProcB:

Address: 0x1234          0x9000      0x8888
        +-------------+ +---------+ +---------+
        |             | | Private | |         |
ProcA   |             | | PLT/GOT | |         |
        | Shared      | +---------+ | Shared  |
========| application |=============| library |==
        | code        | +---------+ | code    |
        |             | | Private | |         |
ProcB   |             | | PLT/GOT | |         |
        +-------------+ +---------+ +---------+
Address: 0x2020          0x9000      0x6666

Cet exemple particulier montre un cas simple où le PLT est mappé à un emplacement fixe. Dans votre scénario , il est situé par rapport au compteur de programme actuel, comme en témoigne votre recherche relative au compteur de programme:

<printf@plt+0>: jmpq  *0x2004c2(%rip)  ; 0x600860 <_GOT_+24>

Je viens d'utiliser l'adressage fixe pour simplifier l'exemple.

La façon originale de partage du code signifiait qu'ils devaient être chargés au même emplacement mémoire dans chaque espace d'adressage virtuel de chaque processus qui l'a utilisé. Soit cela, soit il ne pouvait pas être partagé, car le fait de réparer la copie partagée unique pour un processus remplirait totalement les autres processus là où il se trouvait. mappé à un emplacement différent.

En utilisant un code indépendant de la position, ainsi que le PLT et une table de décalage globale (GOT), le premier appel à une fonction printf@plt (dans le PLT) est une opération en plusieurs étapes, dans laquelle les actions suivantes ont lieu:

  • Tu appelles printf@plt dans le PLT.
  • Il appelle la version GOT (via un pointeur) qui initialement renvoie à un code de configuration dans le PLT.
  • Ce code de configuration charge la bibliothèque partagée pertinente si ce n'est pas encore fait, puis modifie le pointeur GOT pour que les appels suivants directement vers le vrai printf plutôt que le code de configuration PLT.
  • Il appelle ensuite le code printf chargé à l'adresse correcte pour ce processus.

Lors des appels suivants, comme le pointeur GOT a été modifié, l'approche en plusieurs étapes est simplifiée:

  • Tu appelles printf@plt dans le PLT.
  • Il appelle la version GOT (via le pointeur), qui pointe désormais vers le réel printf.

Un bon article peut être trouvé ici , détaillant comment glibc est chargé au moment de l'exécution.

109
paxdiablo

Pas sûr, mais probablement ce que vous avez vu est logique. La première fois que vous exécutez la commande disas, printf n'est pas encore appelé, il n'est donc pas résolu. Une fois que votre programme appelle la méthode printf la première fois que le GOT est mis à jour et maintenant le printf est résolu et le GOT pointe vers la fonction réelle. Ainsi, le prochain appel à la commande disas affiche l'assembly printf réel.

4
rkachach