なんかmajor crunkしたライブラリをmark as obsoleteしてしまったみたいだね。

以前のエントリで、共有ライブラリの「後方」互換について説明しましたが、今度は「前方」互換について。

共有ライブラリの前方互換とは

新しい共有ライブラリをリンクしているアプリケーションが

実行時に古い共有ライブリに置き換えても完全に動作する

ことを保障するということです。

よく似た言葉である後方互換についておさらいすると

古い共有ライブラリをリンクしているアプリケーションが

実行時に新しい共有ライブラリに置き換えても完全に動作する

ことを保障するものでした、前方互換とは視点が逆になってます。

この両者を混同しないようによーく注意してください。

どのような変更を行うとこの前方互換が損なわれるのでしょうか？

それは共有ライブラリに新しい関数や変数(=グローバルシンボル)が追加された場合などです *1 。

foo.h

extern void foo(void);

foo.c

#include <stdio.h> #include "foo.h" void foo() { printf("foo

"); }

これをlibfoo.so.0.0としてコンパイルします。

$ gcc -shared -Wl,-soname,libfoo.so.0 -o libfoo.so.0.0 foo.c $ ln -sf libfoo.so.0.0 libfoo.so.0 $ ln -sf libfoo.so.0 libfoo.so

次にlibfoo.so.0をリンクするアプリケーション、test_fooを作成します。

test_foo.c

#include "foo.h" int main(void) { foo(); }

コンパイルして実行します。

$ gcc -Wl,-rpath,. -L. -lfoo -o test_foo test_foo.c $ ./test_foo foo

次にこのlibfooに新しい関数を追加します。

foo.h

--- foo.h.orig 2008-03-27 19:13:43.000000000 +0900 +++ foo.h 2008-03-27 19:14:00.000000000 +0900 @@ -1 +1,2 @@ extern void foo(void); +extern void bar(void);

foo.c

--- foo.c.orig 2008-03-27 19:13:50.000000000 +0900 +++ foo.c 2008-03-27 19:14:35.000000000 +0900 @@ -6,3 +6,9 @@ { printf("foo

"); } + +void +bar() +{ + printtf("bar

"); +}

これをマイナーバージョンを1つ上げ、libfoo.so.0.1としてコンパイルします。

$ gcc -shared -Wl,-soname,libfoo.so.0 -o libfoo.so.0.1 foo.c $ ln -sf libfoo.so.0.1 libfoo.so.0 $ ln -sf libfoo.so.0 libfoo.so

先ほど作成したtest_fooを再コンパイルなしで実行してみましょう

$ ./test_foo foo

後方互換は壊れていませんので、何のトラブルも無く実行可能です。

では、先ほどマイナーバージョンを変更した時に追加した

関数bar()を呼び出すようにアプリケーションを変更します。

--- foobar.c.orig 2008-03-27 19:26:47.000000000 +0900 +++ foobar.c 2008-03-27 19:26:59.000000000 +0900 @@ -3,4 +3,5 @@ main(void) { foo(); + bar(); }

コンパイルして実行してみましょう。

$ gcc -Wl,-rpath,. -L. -lfoo -o test_foo test_foo.c $ ./test_foo foo bar

こちらも当然ですが、何のトラブルも無く実行可能です。

ではシンボリックリンクであるlibfoo.so.0の実体を

新しいlibfoo.so.0.1から古いlibfoo.so.0.0に巻き戻すとどうなるでしょうか？

$ ls -l libfoo.so.0 lrwxr-xr-x 1 tnozaki tnozaki 13 Mar 27 19:31 libfoo.so.0 -> libfoo.so.0.1 $ rm -f libfoo.so.0 $ ln -sf libfoo.so.0.0 libfoo.so.0 $ ls -l libfoo.so.0 lrwxr-xr-x 1 tnozaki tnozaki 13 Mar 27 19:37 libfoo.so.0 -> libfoo.so.0.0

先ほど作成したtest_fooを再コンパイルなしで実行してみましょう

$ ./test_foo foo ./test_foo: Undefined PLT symbol "bar" (symnum = 15)

barシンボルを解決できず、実行時エラーになってしまいました。

これが「前方互換性がない」状態という訳です。

他にも、構造体の最後に新規にメンバが追加された場合なんかも

後方互換はOKでも前方互換は失われます、例えば このエントリの

struct lconvの変更がこのケースに該当します *2 。

通常UNIX系OSではライブラリのバージョン管理は

real name lib<ライブラリ名>.so.<メジャー番号>.<マイナー番号>

soname lib<ライブラリ名>.so.<メジャー番号>

linker name lib<ライブラリ名>.so

として扱いますが、メジャーとマイナーの使い分けは

後方互換性が壊れた場合 -> メジャーを変更

前方互換性が壊れた場合 -> マイナーを変更

です、ライブラリ開発者の皆様におかれましては

その日の機嫌や語呂合わせなんかで適当に変えたり変えなかったり

しないように切にお願いする次第です。

後方互換性が壊れた場合には、新旧のメジャーを持つライブラリは残しておく必要があります *3 。

しかし前方互換性に関しては最新のマイナーを持つものがありさえすればノートラブルです

古いものは消しちゃっても構いません *4 、しかし

誤操作などで共有ライブラリを消してしまい、バックアップから戻すようなケースでは

必ず同じマイナーを持つものを戻す

必ず同じマイナーを持つものを戻す pkgsrcやRPMなどのバイナリパッケージは共有ライブラリの

メジャーだけでなくマイナーまで依存関係チェックしないと駄目 *5

ということだけは記憶の片隅に置いといてください。

＃でないと前方互換性の問題でトラブる可能性が。

ってもFreeBSDなんかだとobjformatをELF化した時、SysV系の真似？して

マイナーを廃止してしまい新旧の区別が判らんようですが *6 。

ここまで書いてsource-changesに tsutsuiさんの解説発見、わーいダブった。