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>
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:
printf@plt
dans le PLT.printf
plutôt que le code de configuration PLT.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:
printf@plt
dans le PLT.printf
.Un bon article peut être trouvé ici , détaillant comment glibc
est chargé au moment de l'exécution.
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.