上記(LinuxダイナミックリンクのPLTとGOTについて話す(1) -PLTとGOTとは)は、GOTテーブルテクノロジーを使用して動的ライブラリ関数呼び出しを解決するために導入され、PLTはGOTからアドレスを取得して呼び出しを完了します。この前提は、PLTが実行されてすべての機能が完了する前に、GOTがランタイム再配置を完了する必要があることです。
ただし、Linuxの世界では、ほとんどすべての可能なことが遅延され、最終的な修正作業は、後退することが不可能になるまで行われません。一般的なケースは次のとおりです。
fork之后父子进程内存的写时拷贝机制
Linux用户态内存空间分配与物理内存分配机制
C++库的string类写时拷贝机制
もちろん、ダイナミックチェーンの遅延再配置メカニズムも不可欠です。
移転の遅れ
実行可能ファイルから呼び出される動的ライブラリ関数が多数ある場合、これらの関数のアドレス解決と再配置はプロセスの初期化中に実行されるため、プロセスの起動時間が大幅に増加します。したがって、Linuxは遅延再配置メカニズムを提案しており、動的ライブラリー関数が呼び出された場合にのみ、解決と再配置作業に対処します。
プロセスが開始されると、GOTエントリは最初に再配置されず、関数が呼び出されるまで再配置は行われません。このメカニズムを実装するには、GOTエントリが完全に再配置されたかどうかを示す状態が必要です。
明白な解決策は、GOTにステータスビットを追加して、GOTエントリが再配置されたかどうかを記述することです。その場合、各関数には2つのGOTエントリがあります。対応するPLT疑似コードはどうですか:
void printf@plt()
{
if (printf@got[0] != RELOCATED) { // 如果没完成重定位
调用重定位函数
printf@got[1] = 地址解析发现的printf地址;
printf@got[0] = RELOCATED;
}
jmp *printf@got[1];
}
このソリューションでは、関数ごとに2つのGOTエントリを使用し、メモリ使用量が大幅に倍増しています。ただし、GOTテーブルエントリのステータスビットと実際のアドレス項目を注意深く観察してください。これら2つの項目は同時に使用されることはありません。これらの2つの変数を1つのGOT項目に再利用できますか?答えは「はい」です。Linuxダイナミックリンカーは、同様の独創的なソリューションを使用して、これら2つのGOTエントリを1つに結合します。
どうやって?非常に単純です。最初に上記のコードを上下逆にします。
void printf@plt()
{
address_good:
jmp *printf@got // 链接器将printf@got填成下一语句lookup_printf的地址
lookup_printf:
调用重定位函数查找printf地址,并写到printf@got
goto address_good;
}
実行可能ファイルテストにリンクする場合、リンカーはlookup_printfラベルのアドレスにprintf @gotエントリの内容を入力します。
つまり、プログラムが初めてprintfを呼び出すときに、printf @ gotエントリを使用して、printf plt命令の後半に進みます。後半では、動的リンカーにジャンプしてprintfアドレスを解決し、printf @ gotアイテムに再配置します。
したがって、魔法の効果が現れます。printfへの2回目の呼び出しで、printf @ gotを介してprintf関数に直接ジャンプします。
以下は、テスト実行可能ファイルですobjdump -d test> test.asmコマンドを使用して逆コンパイルしてアセンブリコードを生成すると、ジャンププロセス全体を確認できます。
以下はtest.asmファイルのPLT / GOTに関連する部分であり、いくつかの誤解に対するいくつかの修正が変更されました
最初のアイテムのpltテーブルを<common @ plt>アイテムに変更しました。objdump -dの出力結果で誤ったシンボル名が使用されます。これは、この項目にシンボルがないため、objdumpを出力すると、シンボルに近いアドレスを見つけて、誤ったシンボル名を表示してしまうためです。誤解を避けるため、直接削除しています。
各plt命令のJmp * 0xf80496xxは、対応する取得したアイテムにアクセスするためのものです。関数が初めて呼び出される前に、これらの取得された項目の内容はリンカーによって生成され、その値はpltのjmpに対応する次の命令を指します。
以下は、gdbコマンドを使用して、テスト実行可能ファイルで取得した関数のテーブルの内容を次のように表示する方法です。
2つの図を比較すると、前述のルールがわかります。
最後に、すべてのpltが共通の@ pltにジャンプして実行されます。これは、各pltテーブルに対して繰り返される命令ではなく、ダイナミックリンクのシンボリック解決と再配置の共通エントリです。PLT命令の数を減らすために、Linuxはパブリック関数に改良されました。この観点から見ると、Linuxも戦っています。