torutkのブログ

ソフトウェア・エンジニアのブログ

動的共有ライブラリのバージョン管理

d:id:torutk:20081103でBoostライブラリのSONAMEについて調べていました。今回は、自分で作成するプログラムにおいて、動的共有ライブラリファイルのSONAMEの仕組みを使ってバージョン管理をする方法を模索しました。

やりたいこと

C++のプログラムを動的共有ライブラリで作成・提供する際、ライブラリのバージョンアップにおいて、ソース互換性、バイナリ互換性の有無をバージョン番号で区別したい。
また、バイナリ互換性を失うバージョンアップの際は、誤って新しいバージョンのライブラリと古いライブラリ利用コードとを実行時にリンクできないようにしたい。

バージョン番号の付け方

バージョンアップには以下の種類があります。

  • ソース互換性も失う大きなバージョンアップ
  • ソース互換性はあるがバイナリ互換性を失うバージョンアップ
  • ソース互換性もバイナリ互換性もあるバージョンアップ

Linuxのパッケージのバージョン付与方法に似せると、3つの数値の組で以下のように表現します。

I.J.K
記号 更新時の意味
I ソース互換を失う場合に番号を増分する。その際、J.Kは0にリセットする。
J バイナリ互換を失う場合に番号を増分する。その際、Kは0にリセットする。
K バイナリ互換のある場合に番号を増分する。

例えば、現在のバージョン番号が、"3.1.4"とした場合に

  • ソース互換を失う変更を行った場合、バージョン番号を"4.0.0"に更新。
  • ソース互換はあるがバイナリ互換を失う変更を行った場合、バージョン番号を"3.2.0"に更新。
  • バイナリ互換もある変更の場合、バージョン番号を"3.1.5"に更新。
別なバージョン番号付与方法(1)

ソース互換の有無はバージョン番号では明記せずに、バイナリ互換有無だけを識別する方法もあります。

J.K

の2つで表記し、Jの増分有無でバイナリ互換の有無を判定します。この方法ではライブラリファイルのバージョン番号構成が1つ減り、シンプルになります。

別なバージョン番号付与方法(Boostライブラリ)

d:id:torutk:20081103で紹介したように、Boostは公のバージョン番号(例:1.35.1)とは異なる数値を動的共有ライブラリファイルのSONAMEに付けています。これも1つの割り切った方法かと思います。

Linuxの動的共有ライブラリのバージョン管理の仕組み

Linuxの動的共有ライブラリファイル(ELF形式)には、動的リンクで使用される情報が格納された動的セクションがあります。この中に、実行時(動的)リンクでライブラリファイル名に関わる情報としてNEEDEDとSONAMEの2種類が保持されています。

NEEDEDには、その実行ファイルがリンクしているライブラリファイル名が格納されており、このライブラリファイル名から実行時(動的)リンカが実行時にライブラリファイルを検索してリンク処理を行います。このNEEDEDには、通常以下のようなバージョン付きファイル名が格納されます。

$ readelf -d foo | grep NEEDED
 0x00000001 (NEEDED)        Shared library: [libbar.so.2]
 :(略)

これは、実行ファイルfooが動的共有ライブラリファイル"libbar.so.2"に依存していることを示します。
このとき、ライブラリパス上にlibbarの異なる3つのライブラリがあった場合、バイナリ互換を有するライブラリとだけリンクすることが可能となります。

$ ls /usr/lib/libbar*
libbar.so   -> libbar.so.3
libbar.so.1 -> libbar.so.1.23.4
libbar.so.2 -> libbar.so.2.34.5
libbar.so.3 -> libbar.so.3.45.6
libbar.so.1.23.4
libbar.so.2.34.5
libbar.so.3.45.6
$

fooは、その実行ファイルの動的セクションに保持されたNEEDED情報で"libbar.so.2"への依存があるので、実行時リンカはlibbar.so.2を探してこれをリンクしようとします。上の場合、libbar.so.2は、libbar.so.2.34.5へのシンボリックリンクなので、実際にはlibbar.so.2.34.5にリンクされることになります。新しいバイナリ互換性のないバージョン(libbar.so.3)がインストールされていますが、fooは問題なく実行されます。

では、fooの実行ファイルのNEEDEDにどのように"libbar.so.2"が格納されているかを見てみます。

SONAMEを持つライブラリの生成

実は、libbar.so.2.34.5を生成するときに、リンクオプションでSONAMEに"libbar.so.2"を持つように指定しているだけです。

$ g++ -fPIC -c bar.cpp
$ g++ -shared -Wl,-soname,libbar.so.2 -o libbar.so.2.34.5
$

これで生成したlibbar.so.2.34.5の動的セクションのSONAMEを見ると、

$ readelf -d libbar.so.2.34.5 | grep SONAME
 0x0000000e (SONAME)       Library soname: [libbar.so.2]
$

となっています。拡張子が.aや.soでないので、標準ではリンクできないので、シンボリックリンクファイルを作成しておきます。

$ ln -s libbar.so.2.34.5 libbar.so.2
$ ln -s libbar.so.2 libbar.so
ライブラリへのリンク

libbar.soをリンクする実行ファイルを生成します。

$ g++ foo.cpp -o foo -lbar

このとき、libbar.soのシンボリックリンク先であるlibbar.so.2.34.5がリンク処理対象となります。このSONAMEを取り出して、実行ファイルfooの動的セクションNEEDEDに追加され、fooが"libbar.so.2"への依存を持つようになります。

C++でのバージョン管理

ソース互換、バイナリ互換をバージョン番号に含める場合、上で述べた例において、SONAMEにつける番号を2組にします。

$ g++ -fPIC -c bar.cpp
$ g++ -shared -Wl,-soname,libbar.so.2.34 -o libbar.so.2.34.5

以下のように、シンボリックリンクを作成します。

libbar.so -> libbar.so.2.34
libbar.so.2.34 -> libbar.so.2.34.5
libbar.so.2.34.5

便利ツール

readelf

ELFファイルのヘッダ情報やセクション情報を見るツールです。

ldconfig

ライブラリファイルのSONAMEにあるファイルが存在しない場合、そのシンボリックリンクファイルを作成します。

$ ls
libbar.so.2.34.5
$ ldconfig -n .
$ ls
libbar.so.2.34  -> libbar.so.2.34.5
libbar.so.2.34.5
$

残念ながら、"libbar.so"は作ってくれない

LD_DEBUG

環境変数LD_DEBUGを設定すると、実行時リンクの状況がコンソールに出力されます。設定できる値は、

files
bindings
libs
versions

のようです。

nm -D

stripされたライブラリだとnmコマンドでシンボル情報を見ようとしても"no symbols"と返されましたが、-Dオプションを付けるとしっかり見えます。(今回SONAMEを調べていて知った小技なのでメモ)