Titre: Format ELF : relocations, PLT, GOT et section .plt.got Posté par: Pech le 02 Février 2021 à 18:43:00 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/ (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 :
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) :
Et là, il faut maintenant parler de lazy binding vs "now binding" (binaire compilé avec l'option "-Wl,-z,now") :
Revenons donc sur les six sections évoquées plus haut :
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) : Citation $ 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.3 00003ff4 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 : Citation $ readelf -s hello_world | grep "\<_start\>" Résumons :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
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 : Citation $ readelf -r /usr/lib/x86_64-linux-gnu/libpthread-2.28.so Et c'est raté : où est "__libc_stack_end" par exemple ?[...] 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 : [...] 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 :
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 ++ |