プログラミングをやっているとしばしば出てくるこれらの単語...
あまり意味が理解できていなかったのでまとめておきます。
また、次のサイトは非常にわかりやすく解説されている。
位置独立形式(PIC・PIE)の意味もちょっと分かったような気がする。
https://yupo5656.hatenadiary.org/entry/20060618/p1
動的リンク
その名の通り、実行時にリンクが行われること。
静的リンクでは実行前にリンクが行われるため、実行ファイルのサイズが大きくなりがちだが、動的リンクではディスクストレージの節約につながる。
共有ライブラリ
メモリに読み込まれたライブラリを複数のプログラムで共有すること。
動的リンクはディスクストレージの節約を行ったのに対して、共有ライブラリはメモリの節約を行う。
【Hello World の本によると】
共有ライブラリで共有された関数を呼び出すときにはGOT(Global Offset Table)を参照する。
なぜこのようなものが必要かというと、共有ライブラリのアドレスがそれを参照する複数のプログラムから見た場合、それぞれ異なって見えるから。(だって、プロセスはそれぞれ異なるベースアドレスをもった異なるセグメントに置かれる)
なので、共有ライブラリ中の関数を絶対値で指定することは不可能。
ここで考えられたのがGOTで、ポインタ経由で関数を呼び出そうとした。
共有ライブラリはプロセスごとに異なるアドレスにロードされても、実行コードはそのまま共有できるのだ。(位置独立コード)
このような関数へのポインタをGOTとよび、GOTを参照して関数を呼び出す処理を行うようなものの集まりをPLT(Procedure Linkage Table)という。
このポインタの初期化は起動時にまとめてセットされるのではなく、初回の関数呼び出し時に行われる。(これがデフォルトだが、オプションによっては実行開始時にダイナミックリンカにすべてのGOTに対してアドレスを埋め込ませることも可能である)
実は起動時にはGOTにはアドレス解決を行うコードが格納されていて、関数の呼び出しが初めて行われるとこの関数が実行されGOTに正しいアドレスが格納されるのだとか。
なので二回目以降の呼び出しは、呼び出し先の関数に直接ジャンプする。
共有ライブラリなどのプログラムは位置独立コードが使用され、gccでは-fpicを指定するといい。
では実際のコードをちょっとだけ見て行きたいと思う。
このコードはmainからprintfを読んでいるだけの簡単なコードで、もちろん共有ライブラリが使用されている。
#include<stdio.h>
int main(void){
printf("hello world\n");
return 0;
}
このプログラムのアセンブラを覗いてみると、printfの呼び出しの部分には次のような記述がされていた。
08483e5 : e8 0a ff ff ff call 80482f4 <printf@plt>
つまり、このcallでは0x80482f4へとジャンプする命令である。
そして、0x80482f4のアドレスを実際に覗いてみると次のようになっていた。
80482f4 : jmp *0x804966c
80482fa : push $0x10
80482ff : jmp 80482c4 <_init+0x30>
「jmp *0x804966c」の部分から0x804966cにはGOTがあるのではないかと推測できる。
「readelf -a」を使用して実際に見てみると「.rel.plt」というセクションが置かれているようだ。
0x804966cの内容に対応する実行ファイルの部分を見てみると「0x80482fa」という値が入っている。
これは「push $0x10」を行っているアドレスだ。
この値はGOTをセットする関数がどのGOTを設定すればよいのか判断するのに使用される。
また、プログラムが始まってからの最初の呼び出しでは、まだ関数ポインタが正しく初期化されていないので次のアドレスにジャンプし、0x80482ffのjmpで設定されるようになっているのだと思う。
そして、push $0x10の値をセレクタとして、「jmp 80482c4」の先でprintfに対応するGOTの部分(この場合は0x804966cの部分)が設定され、二回目の呼び出しからは一番上のjmp命令でprintfが呼び出されるようになるのだろう。