オペレーティング システムは通常、プログラム実行の効率を向上させるために動的リンクを使用します。ダイナミック リンクの場合、リンク ライブラリ内のすべての関数は、プログラムのロード時に同時にロードされるのではなく、プログラムの実行時にオンデマンドでロードされます。関数が呼び出されない場合、その関数はロードされません。プログラムが人生に組み込まれます。この設計により、プログラム動作のスムーズさが向上し、メモリ容量が削減されます。さらに、最新のオペレーティング システムではコード セグメントの変更は許可されず、データ セグメントの変更のみが許可されるため、GOT テーブルと PLT テーブルが登場しました。
例を簡単に見てみましょう
続いてみましょうscanf@plt を使用してください
3 行のコードがあることがわかります
jmp 一个地址 push 一个值到栈里面 jmp 一个地址
関数の名前を見ると、これがは、scanf 関数の plt テーブルです。plt が何であるかを理解する必要はありません。何に使用されますか? 引き続き下を見てみましょう。まず、最初の jmp が何であり、どこにジャンプするかを見てみましょう。
実際、これは plt テーブルに対応する関数の取得テーブルであり、0x201020 の値がプッシュ コマンドのアドレスであることがわかります。
1. got テーブルと plt テーブルの意味は何ですか? なぜ飛び回るのでしょうか?
2. got テーブルと plt テーブルの間にはどのような関係がありますか? 対応関係はありますか?
したがって、質問がある場合は、まず答えを見て、それから確認してください。オペレーティング システムは通常、プログラムの動作効率を向上させるために動的リンクを使用しており、動的リンクをプログラムに書き戻すことはできないことを理解する必要があります。コードセグメント。
上記の例では、scanf —> scanf の plt テーブル —> scanf の got table を呼び出していることがわかります。get テーブルの値については、今のところ気にしません。実際の scanf 関数は、プログラムをロードして実行するために取得したテーブルから見つけることができます。
そう考えた後、これは間接アドレス指定プロセスになります。
関数アドレスを格納するデータ セグメントを取得します。この小さなコードは と呼ばれます。 PLT (Procedure Linkage Table) 関数アドレスが格納されるデータセグメントは GOT (Global Offset Table) と呼ばれます。このような考えを形成すると、その中の詳細を注意深く理解できるようになります。
このような一般的なプロセスを理解した後、それがどのように呼び出されるのかを段階的に見てみましょう。解決方法:
1. got テーブルはどのようにして scanf 関数の実際のアドレスを知るのでしょうか?
2. got テーブルと plt テーブルの構造は何ですか?まず plt テーブルを見てみましょう。scanf@plt テーブルのコードの 3 行目が jmp アドレスであることがわかりました。それが何であるかを確認するためにフォローアップしてください。
実際には、これはプログラム PLT テーブルです。(plt[0]) から始まり、その動作は次のとおりです:
push got[1] jmp **got[2]
その後に各関数の PLT テーブルが続きます。このとき、この謎のGOTテーブルをもう一度見てみましょう
この2つ(printf関数とscanf関数のpush 0xnのアドレス、つまり2番目)を除いて、対応するpltテーブルのコードアドレス)、他のgot[1]、got[2]は0なので、pltテーブルは0を持つgotテーブルを指しているのでしょうか?条件を満たしていないため、最新のオペレーティング システムではコード セグメントの変更は許可されておらず、ライトバックであるデータ セグメントの変更のみが許可されています。プログラムを実行すると以前のアドレスと保存内容が変わっています その前にリンク中の内容を保存して比較してみましょう
② 寻找printf的plt表 ③ jmp到plt[0] ④ jmp got[2] -> 0x00000 ⑤⑥ printf和scanf的got[3] got[4] -> plt[1] plt[2]的第二条代码的地址 ⑦⑧ 证实上面一点
プログラムを実行して設定しますscanf
にブレークポイント。この時点で scanf@plt テーブルが変更されていることがわかります。get[4]
# の内容を確認してください。
##ここで got[4] アドレスが変更されるまで、プッシュ 0x1 のアドレスでデバッグを続行します。 ここで質問したいのですが時間、ここはどこですか?次に、get[2] で <_dl_fixup> を呼び出して、get[3] のアドレスを変更します;
那么问题就来了,刚才got[2]处不是0吗,怎么现在又是这个(_dl_runtime_resolve)?这就是运行时重定位。
其实got表的前三项是:
got[0]:address of .dynamic section 也就是本ELF动态段(.dynamic段)的装载地址 got[1]:address of link_map object( 编译时填充0)也就是本ELF的link_map数据结构描述符地址,作用:link_map结构,结合.rel.plt段的偏移量,才能真正找到该elf的.rel.plt got[2]:address of _dl_runtime_resolve function (编译时填充为0) 也就是_dl_runtime_resolve函数的地址,来得到真正的函数地址,回写到对应的got表位置中。
那么此刻,got表怎么知道scanf函数的真实地址?
这个问题已经解决了。我们可以看一下其中的装载过程:
说到这个,可以看到在_dl_runtimw_resolve之前和之后,会将真正的函数地址,也就是glibc运行库中的函数的地址,回写到代码段,就是got[n](n>=3)中。也就是说在函数第一次调用时,才通过连接器动态解析并加载到.got.plt中,而这个过程称之为延时加载或者惰性加载。
到这里,也要接近尾声了,当第二次调用同一个函数的时候,就不会与第一次一样那么麻烦了,因为got[n]中已经有了真实地址,直接jmp该地址即可。
以上がGOTテーブルとPLTテーブルを深く理解する方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。