ライブラリについてまとめてみる。
FreeBSDをベースに書くけど、Linuxとかも基本同じ。OS Xは少し違うけど、だいたい同じ。
たぶん、ここを読むよりもBinary Hacks読めって感じだけど、私はBinary Hacks持ってないし、自分で整理のために書く。
登場人物
- a.out 実行ファイル。今はa.out形式じゃなくてelfだけど、伝統的にデフォルトのファイル名はa.out。
- *.o オブジェクトファイル。コンパイラやアセンブラが生成したもの。関数や変数が入っていると思えば良い。
- lib*.a 静的ライブラリ。*.oをまとめたもの。リンク時に実行ファイルにリンクされる。
- lib*.so 動的ライブラリ。*.oをまとめたもの。実行時に実行ファイルから読み込まれる。
- lib*.la libtoolが生成したテキストファイル。
基礎
実行ファイルを作る
1.以下のファイルを作る
int main(int argc, char *argv[])
{
return 0;
}
2.以下のコマンドでコンパイルしてオブジェクトファイルを作る
% cc -c test0.c
3.以下のコマンドでリンクして実行ファイルを作る
% cc -o test0 test0.o
4.以下のコマンドでリンクされている動的ライブラリを見る
% ldd test0
test0:
libc.so.7 => /lib/libc.so.7 (0x80081d000)
(OS Xの場合はlddの代わりにotool -L
で)
特にコンパイラにリンクするライブラリを指定していないのに、libcが出てくるのは、C言語で書いたプログラムはデフォルトでCの標準ライブラリ(とランタイム)がリンクされるため。
ちなみに、ccの実体はFreeBSD 9系ではgcc、FreeBSD 10系ではclangとなっている(hard linkされている)。(もっと昔のFreeBSDでは、BSD由来のccだったと思う)
静的ライブラリを作る
1.以下のファイルを作る
int func1(void)
{
return 5;
}
2.以下のコマンドでオブジェクトファイルを作る
% cc -c func1.c
3.以下のコマンドで静的ライブラリを作る
% ar -rc libfunc.a func1.o
4.試しにリンクしてみる
int func1(void);
int main(int argc, char *argv[])
{
return func1();
}
% cc -o test1 test1.c -L. -lfunc1
このとき、ccに-vオプションをつけると、コンパイラドライバ(cc)が何を呼び出しているかがわかる。
動的ライブラリを作る
1.以下のファイルを作る
int func2(void)
{
return 6;
}
2.以下のコマンドでオブジェクトファイルを作る
% cc -c -fPIC func2.c
-fPICについては、興味がある人は調べてみてください。
3.以下のコマンドで動的ライブラリを作る
% cc -shared -o libfunc2.so func2.o
4.試しにリンクしてみる
int func2(void);
int main(int argc, char *argv[])
{
return func2();
}
% cc -o test2 test2.c -L. -lfunc2
5.以下のコマンドでリンクされている動的ライブラリを見る(&実行する)
% ldd test2
test2:
libfunc2.so => not found (0)
libc.so.7 => /lib/libc.so.7 (0x80081d000)
% ./test2
Shared object "libfunc2.so" not found, required by "test2"
libfunc2.soがリンクされているが、どこにあるかわからないためlddは not found を表示し、実行するとエラーになる。
6.リンク時に動的ライブラリの場所を教えてやる
% cc -o test2 test2.c -L. -lfunc2 -Wl,-rpath,.
% ldd test2
test2:
libfunc2.so => ./libfunc2.so (0x80081d000)
libc.so.7 => /lib/libc.so.7 (0x800a1e000)
-Wlオプションはリンカ(ld)へのオプションをccに教えて上げるためのオプション。-rpathは動的ライブラリのサーチパスを指定するもの。
ここでは、説明のために.(カレントディレクトリ)を指定しているが、通常-rpathに相対パスを指定することはない。
ライブラリはどこから探されるのか?
リンク時
コンパイル/リンク時に探される場所は、システムで定義されたディレクトリと、コンパイルオプション -L で指定したディレクトリになる。
システムで定義されたディレクトリとは、コンパイラにハードコードされたディレクトリと言い換えても良い。通常、/lib, /usr/lib と、コンパイラ独自のディレクトリ(/usr/lib/gccとか)になる。
もうちょっと厳密には、動的ライブラリのサーチパスはFreeBSDのldのmanによると以下のようになる。
- ldのオプション
-rpath-link
で指定されたディレクトリ - ldのオプション
-rpath
で指定されたディレクトリ - ELFシステムでは
-rpath
と-rpath-link
は使用せず、環境変数LD_RUN_PATHで指定されたディレクトリ - SunOSでは、
-rpath
は使用されず、-Lオプションのみが有効 - 環境変数
LD_LIBRARY_PATH
で指定されたディレクトリ - 環境変数
DT_RUNPATH
もしくはDT_RPATH
で指定されたディレクトリ - /lib, /usr/lib等のデフォルトディレクトリ
- /etc/ld.soconf で指定されたディレクトリ
ただ、実際に試してみるとちょっと違う挙動をしているように見えるので、マニュアルを鵜呑みにはできなさそう。(端折って訳しているが、原文にはnative linker
、native ELF linker
、native linker on an ELF system
のような用語が入り乱れており、私の理解が足りないだけかも知れない)
実行時
静的ライブラリは実行ファイルに直接埋め込まれるので、実行時のパスは関係ない。
動的ライブラリは、以下の順で検索される。(FreeBSDのman rtld
より)
- 参照オブジェクトの
DT_RPATH
(参照オブジェクトにDT_RUNPATH
tagがない場合) - プログラムの
DT_RPATH
(参照オブジェクトにDT_RUNPATH
tagがない場合) - 環境変数
LD_LIBRARY_PATH
で指定されるディレクトリ - 参照オブジェクトの
DT_RUNPATH
tag -
ldconfig(8)
から得られるヒント - /lib, /usr/libディレクトリ(
-z nodefaultlib
オプションをつけてリンクされていない場合)
1とか2は、コンパイル時の-rpath
オプションだと思っておけば良いと思われる。
5はman ldconfig
に詳しいが、FreeBSD 10のデフォルトインストール状態では/etc/defaults/rc.conf
の ldconfig_paths
で指定されたディレクトリ(/usr/lib/compat /usr/local/lib /usr/local/lib/compat/pkg
)と、ldconfig_local_dirs
で指定されたディレクトリ(/usr/local/libdata/ldconfig
)にあるファイルに書かれたディレクトリが対象となる。
これに、/etc/ld-elf.so.conf
ファイルがあればそこに書かれたディレクトリも追加される。
-rpathと-rpath-link
ここで、-rpathと-rpath-linkについてもう少し触れておく。
-rpath-linkが必要になるのは、作成する動的ライブラリが別の動的ライブラリを参照する場合だ。
最終的に、作成したコマンドを/usr/local/bin、ライブラリを/usr/local/libにインストールするケースを考える。
1.新たに、libfunc2.soを利用する動的ライブラリ、libfunc3.soを作成する。
int func2(void);
int func3(void)
{
return func2();
}
% cc -c -fPIC func3.c
% cc -shared -o libfunc3.so func3.o -L. -lfunc2 -Wl,-rpath,/usr/local/lib
このコマンドラインは、リンカがlibfunc2.soを探すのは-Lオプションの.から、実行時にlibfunc2.soを探すのは/usr/local/libから、と言う意味だ。
2.続いて、libfunc3.soを利用するコマンドを作成する。
int func3(void);
int main(int argc, char *argv[])
{
return func3();
}
% cc -o test3 test3.c -L. -lfunc3 -Wl,-rpath,/usr/local/lib -Wl,-rpath-link,.
コマンドを実行するときにlibfunc3.soを探して欲しいのは/usr/local/libなので、-rpathには/usr/local/libを指定する、しかし、現時点ではまだlibfunc3.soが参照するlibfunc2.soは/usr/local/libにはないので、-rpath-linkで場所を教えてやる。
この段階では、ライブラリは適切な場所にないので、lddすると
% ldd test3
test3:
libfunc3.so => not found (0)
libc.so.7 => /lib/libc.so.7 (0x80081d000)
となる。
これを、最終的な場所にinstallしてやると、ライブラリが見つけられるようになる。
% sudo install -c test3 /usr/local/bin/
% sudo install -c libfunc2.so /usr/local/lib/
% sudo install -c libfunc3.so /usr/local/lib/
% ldd /usr/local/bin/test3
/usr/local/bin/test3:
libfunc3.so => /usr/local/lib/libfunc3.so (0x80081d000)
libc.so.7 => /lib/libc.so.7 (0x800a1e000)
libfunc2.so => /usr/local/lib/libfunc2.so (0x800dc7000)
本当はlibtoolとlaについても書こうと思ったんだけど、長くなったし力尽きたので、次の機会に。
ここに挙げたサンプルソースは、githubに上げてあるので、自分で試してみたい人は使ってください。
https://github.com/false-git/libstudy