logo Homepage
Pages: [1]
  Imprimer  
Auteur Fil de discussion: Format ELF : relocations, PLT, GOT et section .plt.got  (Lu 2670 fois)
Pech
Profil challenge

Classement : 34/54254

Membre Junior
**
Hors ligne Hors ligne
Messages: 83


Voir le profil
« 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/

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) :
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\>"
    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 :
Citation
$ 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
++
Journalisée
Pages: [1]
  Imprimer  
 
Aller à: