Bonjour à tous,
Pas de TL;DR; désolé :p
Comme mentionné dans le titre, je vais ici parler de PLT, de GOT et de relocations dans des fichiers ELF.
Si vous êtes déjà perdu, voici un peu de lecture :
https://www.segmentationfault.fr/linux/role-plt-got-ld-so/ Avant de commencer, un point important.
Dans ce qui va suivre, il y a des choses dont je suis certain, mais pour d'autres je ne serais pas aussi affirmatif...
En conséquence :
- à défaut de réponses à mes questions, toute confirmation (ou pas) est la bienvenue
- si vous pensez avoir appris quelque chose, lisez d'éventuels commentaires qui pourraient affirmer le contraire
Pour information, je vais me limiter au cas x86 et chaque fichier aura du contenu dynamique (sinon il n'y a rien à raconter).
Considérons donc un fichier ELF tout ce qu'il y a de plus basique. On va -potentiellement- trouver (en autres) :
- deux sections de relocations : .rel[a].dyn et .rel[a].plt
- deux sections PLT (la PLT "splittée" en deux) : .plt et .plt.got
- deux sections GOT (la GOT "splittée" en deux) : .got et .got.plt
Trois de ces sections vont ensemble : une relocation dans .rel.plt = une entrée dans .plt = une entrée dans .got.plt
Et là, il faut maintenant parler de lazy binding vs "now binding" (binaire compilé avec l'option "-Wl,-z,now") :
- lazy binding : les adresses sont résolues au runtime (premier appel = on résout l'adresse)
- "now binding" : les adresses sont résolues par le dynamic loader (ce qui permet de mettre la GOT en read-only mais c'est pas le sujet)
Revenons donc sur les six sections évoquées plus haut :
- .got.plt = la GOT pour les adresses résolues en utilisant le lazy binding
- .got = la GOT pour les adresses résolues en utilisant du "now binding" (potentiellement présente même si le fichier utilise le lazy binding)
- .plt = la PLT pour les imports de fonctions (dont les adresses seront résolues par lazy binding ou non)
- .rel.plt = les relocations liées à la section .plt
- .rel.dyn = les relocations liées à tout le reste (.got mais aussi .data, .bss, etc)
Dans le cas "now binding", il n'y a donc pas de .got.plt, ou plus exactement elle est "intégrée" dans .got.
Si vous savez compter et que vous me lisez avec attention (merci et bravo !), vous avez remarqué qu'il manque .plt.got dans la liste ci-dessus.
La raison est simple : c'est ici que je coince.
Le seul point donc je suis à peu près sûr c'est qu'elle est présente uniquement pour les fichiers ET_DYN, autrement dit en cas de position-independant code.
Regardons un simple "hello world" codé en C et compilé avec GCC (8.3.0 sur une Debian 10 64 bits) :
$ gcc -m32 -o hello_world hello_world.c
$ file hello_world
hello_world: ELF 32-bit LSB pie executable, Intel 80386, [etc]
$ readelf -S hello_world
[...]
[ 9] .rel.dyn REL 00000368 000368 000040 08 A 5 0 4
[10] .rel.plt REL 000003a8 0003a8 000010 08 AI 5 23 4
[...]
[12] .plt PROGBITS 00001020 001020 000030 04 AX 0 0 16
[13] .plt.got PROGBITS 00001050 001050 000008 08 AX 0 0 8
[...]
[22] .got PROGBITS 00003fec 002fec 000014 04 WA 0 0 4
[23] .got.plt PROGBITS 00004000 003000 000014 04 WA 0 0 4
[...]
$ readelf -r hello_world
[...]
Relocation section '.rel.dyn' at offset 0x368 contains 8 entries:
Offset Info Type Sym. Value Symbol's Name
00003ef4 00000008 R_386_RELATIVE
00003ef8 00000008 R_386_RELATIVE
00003ff8 00000008 R_386_RELATIVE
00004018 00000008 R_386_RELATIVE
00003fec 00000106 R_386_GLOB_DAT 00000000 _ITM_deregisterTMCloneTable
00003ff0 00000206 R_386_GLOB_DAT 00000000
__cxa_finalize@GLIBC_2.1.300003ff4 00000406 R_386_GLOB_DAT 00000000 __gmon_start__
00003ffc 00000606 R_386_GLOB_DAT 00000000 _ITM_registerTMCloneTable
Relocation section '.rel.plt' at offset 0x3a8 contains 2 entries:
Offset Info Type Sym. Value Symbol's Name
0000400c 00000307 R_386_JUMP_SLOT 00000000 puts@GLIBC_2.0
00004010 00000507 R_386_JUMP_SLOT 00000000 __libc_start_main@GLIBC_2.0
$ objdump -D hello_world | grep "\.plt\.got" -A 10
Déassemblage de la section .plt.got :
00001050 <__cxa_finalize@plt>:
1050: ff a3 f0 ff ff ff jmp *-0x10(%ebx)
1056: 66 90 xchg %ax,%ax
Déassemblage de la section .text :
[...]
On va ignorer les sections .plt, .rel.plt et .got.plt (là-dessus, pas de problème) et s'intéresser pour commencer à .got.
On y trouve quatre entrées (les quatre R_386_GLOB_DAT).
Jetons un coup d'oeil sur ce qui se passe au runtime :
$ readelf -s hello_world | grep "\<_start\>"
66: 00001060 54 FUNC GLOBAL DEFAULT 14 _start
$ gdb hello_world
Reading symbols from hello_world...done.
(gdb) b _start
Breakpoint 1 at 0x1060
(gdb) r
Starting program: /home/pech/Bureau/hello_world
Breakpoint 1, 0x56556060 in _start ()
=> 0x56556060 <_start+0>: 31 ed xor ebp,ebp # => la .got est en 0x56556060 - 0x1060 + 0x3fec = 0x56558fec (même segment que .text)
(gdb) x/4x 0x56558fec
0x56558fec: 0x00000000 0xf7e0fd20 0x00000000 0x56556199
(gdb) x 0xf7e0fd20
0xf7e0fd20 <__cxa_finalize>: 0x105e04e8
(gdb) x 0x56556199
0x56556199 <main>: 0x04244c8d
Résumons :
- "_ITM_deregisterTMCloneTable" et "__gmon_start__" ne sont pas résolus
- "__cxa_finalize" est résolu
- "_ITM_registerTMCloneTable" est résolu et son adresse est celle de... "main" oO
Disons qu'il ne s'est rien passé et faisons un petit aparté sur "__gmon_start__".
Si j'en crois mes recherches, il s'agit d'une fonction qui sert à démarrer le profiling (avec gprof).
Quand on compile avec l'option "-pg", elle est appelée dans "_init", sinon le symbole ne sert à rien.
Sauf qu'elle est alors dans la section .text et qu'il n'y a pas de relocations.
Bref, regardons maintenant la .plt.got : on n'y trouve qu'une entrée. Toute la question est : pourquoi ?
On remarque que c'est le seul symbole qui est importé parmi les quatre R_386_GLOB_DAT : peut-être est-ce un début de réponse ?
Oui, mais... peut-être pas :
$ readelf -r /usr/lib/x86_64-linux-gnu/libpthread-2.28.so
[...]
Relocation section '.rela.dyn' at offset 0x4e88 contains 69 entries:
[...]
000000000001bfb0 0000000800000006 R_X86_64_GLOB_DAT 0000000000000000 _ITM_deregisterTMCloneTable + 0
000000000001bfb8 0000000c00000012 R_X86_64_TPOFF64 0000000000000000 errno@GLIBC_PRIVATE + 0
000000000001bfc0 0000001100000012 R_X86_64_TPOFF64 0000000000000000 __resp@GLIBC_PRIVATE + 0
000000000001bfc8 0000002e00000006 R_X86_64_GLOB_DAT 0000000000000000
__libc_stack_end@GLIBC_2.2.5 + 0
000000000001bfd0 0000003400000006 R_X86_64_GLOB_DAT 0000000000000000 _rtld_global_ro@GLIBC_PRIVATE + 0
000000000001bfd8 0000004a00000006 R_X86_64_GLOB_DAT 0000000000000000 __libc_vfork@GLIBC_PRIVATE + 0
000000000001bfe0 0000004d00000012 R_X86_64_TPOFF64 0000000000000000 __h_errno@GLIBC_PRIVATE + 0
000000000001bfe8 0000005a00000006 R_X86_64_GLOB_DAT 0000000000000000 _ITM_registerTMCloneTable + 0
000000000001bff0 0000005e00000006 R_X86_64_GLOB_DAT 0000000000000000 _rtld_global@GLIBC_PRIVATE + 0
000000000001bff8 0000006100000006 R_X86_64_GLOB_DAT 0000000000000000
__cxa_finalize@GLIBC_2.2.5 + 0
Relocation section '.rela.plt' at offset 0x5500 contains 88 entries:
[...]
$ objdump -D /usr/lib/x86_64-linux-gnu/libpthread-2.28.so | grep "\.plt\.got" -A 10
Déassemblage de la section .plt.got :
00000000000065a0 <__cxa_finalize@plt>:
65a0: ff 25 52 5a 01 00 jmpq *0x15a52(%rip) # 1bff8 <
__cxa_finalize@GLIBC_2.2.5>
65a6: 66 90 xchg %ax,%ax
Déassemblage de la section .text :
[...]
Et c'est raté : où est "__libc_stack_end" par exemple ?
Une hypothèse qui pourrait casser tout ce que j'ai écrit : peut-être que le contenu du binaire que crée GCC à changé depuis je-ne-sais-quelle version (c'est le cas pour les program headers par exemple) ?
En résumé, je suis complètement perdu :'(
Pour reprendre l'exemple du "hello world" plus haut :
- c'est quoi cette histoire de "_ITM_registerTMCloneTable" qui est résolu en "main" ??
- y-a-t'il un quelconque intérêt à mettre des relocations inutiles ("__gmon_start__") ?
Pour ce qui est de "__gmon_start__", la relocation est d'autant plus inutile que quand l'option "-pg" est passée au compilateur, la fonction est copiée dans .text donc c'est quoi ce ... ??
Sauf que je l'ai aussi parfois vue dans .plt.got (une vieille version de libgcc_s.so.1 par exemple), d'où l'hypothèse d'un changement au niveau du fonctionnement de GCC.
Et enfin : qu'y a-t'il dans cette section .plt.got ? De manière plus originale, est-il possible de "deviner" son contenu sans désassembler (i.e. à partir de .rel[a].dyn, .dynsym, ou autre) ?
Si vous avez des explications, je suis preneur parce que là, je
nage coule
++