前の記事(Linuxダイナミックリンク(2)でのPLTとGOTについて-遅延再配置)では、すべてのダイナミックライブラリ関数のplt命令が最終的にパブリックplt実行にジャンプするので、パブリックplt命令のアドレスは何ですか?
テスト実行可能ファイルのパブリックpltを貼り付けます。
080482a0 <common@plt>:
80482a0: pushl 0x80496f0
80482a6: jmp *0x80496f4
...
最初の文、pushl 0x80496f0は、アドレスをスタックにプッシュすることです。つまり、最後に呼び出された関数にパラメーターを渡します。
2番目の文、jmp * 0x80496f4は、実行する最後の関数にジャンプすることですが、考えられることは、動的ライブラリ関数のアドレスを解決できるコードにジャンプすることです。
0x80496f4聖人はどこですか?gdbデバッガーを使用して、それを引き出します。
$ gdb -q ./test
...
(gdb)x/xw 0x80496f4
0x80496f4 <_GLOBAL_OFFSET_TABLE_+8>: 0x00000000
(gdb) b main
Breakpoint 1 at 0x80483f3
(gdb) r
Starting program: /home/ivan/test/test/test
Breakpoint 1, 0x80483f3 in main ()
(gdb) x/xw 0x80496f4
0x80496f4 <_GLOBAL_OFFSET_TABLE_+8>: 0xf7ff06a0
デバッグプロセスから、0x80496f4はGOTテーブルのアイテムに属していることがわかります。プロセスが実行されていない場合、その値は0x00000000です。プロセスが実行されている場合、その値は0xf7ff06a0になります。さらにデバッグを行うと、このアドレスは動的リンカーにあり、対応する関数は_dl_runtime_resolveです。
さて、あなたは何か考えましたか?すべての動的ライブラリ関数は、最初に呼び出されたときに、XXX @ plt-> public @ plt-> _dl_runtime_resolve呼び出し関係によるアドレス解決と再配置に使用されます。
そういえば、実際にはパズルに対する答えはありません。例としてprintf関数を考えてみましょう。
_dl_runtime_resolve是怎么知要查找printf函数的
_dl_runtime_resolve找到printf函数地址之后,它怎么知道回填到哪个GOT表项
到底_dl_runtime_resolve是什么时候被写到GOT表的
最初の2つの質問では、必要な情報は1つだけです。この情報は、関数に対応するxxx @ pltテーブルで非表示になっています。例として、printf @ pltを考えます。
printf@plt>:
jmp *0x80496f8
push $0x00
jmp common@plt
2番目の命令は秘密です。各xxx @ pltの2番目の命令プッシュのオペランドは異なり、これは関数のIDと同等であり、ダイナミックリンカーはどの関数を解析するかを知ることができます。 。
そのような神はいますか?これは神ではなく、コンパイラリンカとダイナミックリンカによって意図的に配置された偶然の一致です。
readelf -r testコマンドを使用して、.rel.pltセクションに大きな秘密があるテスト実行可能ファイルの再配置情報を表示します。
$ readelf -r test
....
Relocation section '.rel.plt' at offset 0x25c contains 3 entries:
Offset Info Type Sym.Value Sym. Name
080496f8 00000107 R_386_JUMP_SLOT 00000000 puts
080496fc 00000207 R_386_JUMP_SLOT 00000000 __gmon_start__
08049700 00000407 R_386_JUMP_SLOT 000000000 __libc_start_main
プッシュルックPLT機能命令の動作回数:
printf関数の対応する0x0のプッシュ
gmon_start 0x8というプッシュに対応する
対応プッシュ0x10の__libc_start_main
これらの3つのプッシュオペランドは、.rel.pltセクションの3つの関数のオフセットに正確に対応しています。_dl_runtime_resolve関数では、オフセットと.rel.pltセクションの情報に基づいて、解決する関数がわかります。GOTエントリのアドレスである.rel.pltの左端にあるオフセットフィールドをもう一度見てください。つまり、_dl_runtime_resolveがシンボルの解決を完了した後、書き戻されたスペースを再配置します。
3番目の質問:_dl_runtime_resolveがGOTテーブルに書き込まれたのはいつですか?
答えは簡単です.exeによってLinuxカーネルがロードされた後、実行可能ファイルは直接実行されませんが、最初に動的リンカー(ld-linux-XXX)にジャンプして実行されます。_dl_runtime_resolveアドレスをld-linux-XXXのGOTエントリに書き込みます。
実際、i386アーキテクチャでは、GOTエントリに_dl_runtime_resolveアドレスが事前に書き込まれるだけでなく、GOTエントリを占有する各関数に加えて、GOTエントリは3つのパブリックエントリを保持します。 3つのアイテム、個別に保存:
got [0]:ELF動的セグメント(.dynamicセグメント)のロードアドレス
got [1]:ELFのlink_mapデータ構造記述子の
アドレスgot [2]:_dl_runtime_resolve関数のアドレス
ELFをロードした後、ダイナミックリンカーはこれら3つのアドレスをGOTテーブルの最初の3つの項目に書き込みます。
実際、上記のパブリックplt命令には分析されないオペランドがあります。実際、これはgot [1](このELFのlink_map)のアドレスです。これは、link_map構造体と.rel.pltセクションのオフセットを組み合わせるだけで、エルフの.rel.pltエントリを実際に見つけてください。
関心のある読者はgdbを使用できます。main関数が実行されたら、GOTテーブルの3つのデータを調べて確認します。
ここで、PLTとGOTのメカニズムをより明確に理解していますか?最後の記事では、グラフィック構造を使用して、PLT / GOTメカニズム全体をつなぎ合わせます。