4    シェアード・ライブラリ

シェアード・ライブラリは,省略時のシステム・ライブラリです。 C コンパイラがコンパイルとリンクを行う際,省略時の設定ではシェアード・ライブラリを使用します。

この章では,次の内容について説明します。

4.1    シェアード・ライブラリの概要

シェアード・ライブラリは,メモリ内のどのアドレスへも配置できる実行可能ファイル・コードから構成されています。 シェアード・ライブラリの命令のコピーは 1 つだけロードされます。 アーカイブ (静的) ライブラリのようにライブラリを使用する各プログラムがライブラリのコピーをロードするのではなく,複数のプログラム間で 1 つのコピーを共用します。

シェアード・ライブラリを使用するプログラムは,次の点でアーカイブ・ライブラリを使用するプログラムよりもはるかに有利です。

シェアード・ライブラリの使用はユーザからは透過です。 さらに,プログラムをアーカイブ・ライブラリとリンクさせる方法を採用することもできます。 また,ユーザ独自のシェアード・ライブラリを作成し,他のユーザに使用させることもできます。 大部分のオブジェクト・ファイルおよびアーカイブ・ライブラリは,シェアード・ライブラリにすることができます。 シェアード・ライブラリにできるファイルについては,4.5 節を参照してください。

シェアード・ライブラリは,次の点でアーカイブ・ライブラリと異なります。

図 4-1 に,アーカイブ・ライブラリとシェアード・ライブラリの違いを示します。

図 4-1:  アーカイブ・ライブラリとシェアード・ライブラリ

4.2    シンボルの解決

シンボルの解決とは,プログラムまたはシェアード・ライブラリによりインポートされた未解決のシンボルを,そのシンボルをエクスポートするシェアード・ライブラリのパス名にマップする処理です。 アーカイブ・ライブラリおよびシェアード・ライブラリのシンボル解決は,シェアード・オブジェクトのシンボルの最終的な解決がプログラム起動時まで行われないという点を除いて,ほとんど同じ方法で行なわれます。

次の各項では,以下の項目について説明します。

4.2.1    リンカの探索パス

リンカ (ld) は -l オプションによって指定されたファイルを探索する場合,次に示した順序で各ディレクトリで探索します。 リンカは,最初にシェアード・ライブラリ (.so) を探索します。

  1. /usr/shlib

  2. /usr/ccs/lib

  3. /usr/lib/cmplrs/cc

  4. /usr/lib

  5. /usr/local/lib

  6. /var/shlib

シェアード・ライブラリがない場合は,リンカはすべてのディレクトリでアーカイブ (.a) ライブラリを探索します。 -no_archive オプションを使用すると,リンカによるアーカイブ・ライブラリの探索を禁止することができます。

4.2.2    実行時ローダの探索パス

特に指示がない限り,実行時ローダ (/sbin/loader) は,リンカと同じ探索パスを経由します。 実行時ローダに次のように指示して,省略時の探索パスで指定されているディレクトリ以外のディレクトリを探索することができます。

これらの手順で定義されたパスの中で必要とするライブラリをローダが探索できない場合,ローダは4.2.1 項で説明されている省略時のパスの中のディレクトリを探索します。 さらに,_RLD_ROOT 環境変数を使用して,実行時ローダの探索パスを変更することもできます。 詳細については, loader(5) を参照してください。

4.2.3    名前の解決

シンボル名の解決の意味規則は,シンボルが入っているオブジェクト・ファイルまたは共用オブジェクトがリンク・コマンド行に現れる順序に基づいています。 通常,リンカは一番左側の定義を解決しなければならないシンボルと見なします。

リンク・コマンド行が実行可能ファイルに保存されているかのように,名前が順番に解決されます。 プログラムを実行する場合には,実行時にアクセスされるすべてのシンボルが解決されなければなりません。 シンボルが解決されないと,ローダはプログラムの実行を打ち切ります。

未解決のシンボルに関してシステムの動作を判断する方法の詳細については,4.2.4 項を参照してください。

次の順序でメイン・プログラムまたはライブラリへのシンボルの参照が解決されます。

  1. メイン実行可能ファイルを生成する元になったオブジェクト・ライブラリまたはアーカイブ・ライブラリでシンボルが定義されている場合,そのシンボルはメイン・プログラム・ファイルおよびそれが使用するすべてのシェアード・ライブラリで使用される。

  2. シンボルが手順 1 で定義されないで,リンクされている 1 つまたは複数の共用オブジェクトで定義される場合には,定義を指定するリンク・コマンド行の一番左端のライブラリが使用される。

  3. リンク・コマンド行に指定したライブラリが他のライブラリに依存するようにリンクされた場合には,ライブラリの依存性は,深さ優先の方法で探索されるのではなく,広さ優先の方法で探索される。

    たとえば,次の図のように,実行可能プログラム A がシェアード・ライブラリ B および D にリンクされ,シェアード・ライブラリ B がライブラリ C にリンクされているとします。

                                 A
                                / \
                               B   D
                              /
                             C
     
    

    この場合,探索順序は A→B→D→C です。 幅優先探索では,孫にあたるノードはすべての子ノードが探索された後に探索されます。

  4. これまでの手順でシンボルが解決されない場合には,シンボルが解決されないまま残る。

シンボル解決では必ずメイン・オブジェクトのほうが優先されるため,メイン・オブジェクト内で定義されているシンボルにコールバックされるようにシェアード・ライブラリを設定できることに注意してください。 さらに,メイン・オブジェクトは,シェアード・ライブラリの定義を指定変更 (強制排除またはフック) するシンボルを定義することができます。

4.2.4    未解決の外部シンボルの処理のオプション

実行可能プログラムを作成する場合とシェアード・ライブラリを作成する場合では,リンカの省略時の動作は次のように異なります。

-error_unresolved を指定してシェアード・ライブラリを構築すると,未解決のシンボルがある場合でも出力ファイル .so ファイル) が生成され,同名の .so があればそのファイルは上書きされます。 リンカ出力ファイルの上書きについての詳細は 『プログラミング・ガイド』 を参照してください。

リンカが未解決のシンボルをどのように処理するかは,ld コマンドで次のオプションを使用して制御することができます。

これらのオプションについての詳細は, ld(1) を参照してください。

4.3    シェアード・ライブラリとのリンク

プログラムのコンパイルおよびリンクは,シェアード・ライブラリを使用しても,スタティック・ライブラリを使用しても同じ結果になります。 たとえば,次のコマンドは,プログラム hello.c をコンパイルして,省略時のシステムの共用 C ライブラリ libc.so にリンクします。

% cc -o hello hello.c
 

特定の ld コマンドのオプションを cc コマンドに渡すことによって,シェアード・ライブラリ用の探索パスを自由に指定することができます。 たとえば,次に示すように,cc コマンドに -Ldir オプションを使用して,省略時のディレクトリより先に dir を見るように探索パスを変更することができます。

% cc -o hello hello.c -L/usr/person -lmylib
 

省略時のディレクトリを探索から除外し,特定のディレクトリおよび特定のライブラリに探索を限定する場合には,次のように指定します。 まず,引数を付けずに -L オプションを指定し,次に,探索するディレクトリを付けて -L オプションを指定し,その後に探索するライブラリ名を付けて -l オプションを指定します。 たとえば,探索パスを /usr/person とプライベートなライブラリ (libmylib.so) に限定するには,次のコマンドを入力してください。

% cc -o hello hello.c -L -L/usr/person -lmylib
 

cc コマンドは常に暗黙に C ライブラリにリンクしているため,前述の例では,/usr/person ディレクトリ内に libc.so または libc.a のコピーが必要であることに注意してください。

4.4    シェアード・ライブラリの指定解除

アプリケーションをリンクする場合は,省略時の設定としてシェアード・ライブラリが使用されます。 シェアード・ライブラリを使用しないアプリケーションをリンクする場合には,そのアプリケーションをリンクするときに,cc コマンドまたは ld コマンドに -non_shared オプションを使用しなければなりません。 たとえば,次のように入力します。

% cc -non_shared -o hello hello.c
 

大部分のアプリケーションではシェアード・ライブラリは省略時の設定ですが,アプリケーションの中にはシェアード・ライブラリを使用できないものがあります。

4.5    シェアード・ライブラリの作成

シェアード・ライブラリは,-shared オプション付きで ld コマンドを使用して作成します。 オブジェクト・ファイルまたは既存のアーカイブ・ライブラリからシェアード・ライブラリを作成することもできます。

4.5.1    オブジェクト・ファイルからのシェアード・ライブラリの作成

オブジェクト・ファイル bigmod1.o および bigmod2.o から,シェアード・ライブラリ libbig.so を作成するには,次のコマンドを入力します。

% ld -shared -no_archive -o libbig.so bigmod1.o bigmod2.o -lc
 

-no_archive オプションは,シェアード・ライブラリだけを使用して,すべてのシンボルを解決するようにリンカに指示します。 -lc オプションは未解決のシンボルがないかどうか,システムの C ライブラリを探索するようにリンカに指示します。

シェアード・ライブラリを /usr/shlib ディレクトリにコピーして,システム・レベルでの利用を可能にするには,ルート・ディレクトリに関する特権が必要です。 システムで共用するライブラリは,/usr/shlib ディレクトリまたは複数ある省略時のディレクトリのうちいずれかに格納し,各ユーザが LD_LIBRARY_PATH 変数を省略時のパス以外にあるディレクトリに設定しなくても,実行時ローダ (/sbin/loader) がシェアード・ライブラリの場所を探索できるようにしなければなりません。

4.5.2    アーカイブ・ライブラリからのシェアード・ライブラリの作成

ld コマンドを使用して,既存のアーカイブ・ライブラリからシェアード・ライブラリを作成することができます。 次に,スタティック・ライブラリ old.a をシェアード・ライブラリ libold.so に変換する例を示します。

% ld -shared -no_archive -o libold.so -all old.a -none -lc
 

この例では,-all オプションは,アーカイブ・ライブラリ old.a にあるすべてのオブジェクトをリンクするように,リンカに指示しています。 -none オプションは,-all オプションを取り消すようにリンカに指示しています。 -no_archive オプションは,-lc オプションの解決には適用されますが,old.a の解決には適用されない点に注意してください (old.a オプションが明示的に記述されているため)。

4.6    プライベートなシェアード・ライブラリの使用

システムのシェアード・ライブラリだけでなく,どのユーザもプライベートなシェアード・ライブラリを作成および使用することができます。 たとえば,ある共通のコードを共用する 3 つのアプリケーションがあるとします。 これらのアプリケーションは,userdb,および admin という名称であるとします。 共用ファイル io_util.cdefines.c,および network.c に定義されているすべてのシンボルが含まれる共通のシェアード・ライブラリ libcommon.so を作成するには,次の手順に従ってください。

  1. ライブラリの一部となる各 C ファイルをコンパイルする。

    % cc -c io_util.c
    % cc -c defines.c
    % cc -c network.c
     
    

  2. ld コマンドを使用してシェアード・ライブラリ libcommon.so を作成する。

    % ld -shared -no_archive \
    -o libcommon.so io_util.o defines.o network.o -lc
     
    

  3. アプリケーションの一部となる C ファイルをコンパイルする。

    % cc -c user.c
    % cc -o user user.o -L. -lcommon
     
    

    この手順の 2 番目のコマンドは,現在のディレクトリを見てからライブラリ libcommon.so を使用するように,リンカに指示していることに注意してください。

    同じ方法で,db.cadmin.c をコンパイルします。

    % cc -c db.c
    % cc -o db db.o -L. -lcommon
     
    % cc -c admin.c
    % cc -o admin admin.o -L. -lcommon
     
    

  4. libcommon.soLD_LIBRARY_PATH で示されているディレクトリ内にない場合には,libcommon.so をそのディレクトリにコピーする。

  5. コンパイル済みのプログラム (userdb,および admin) をそれぞれ実行する。

4.7    クイックスタートの使用

シェアード・ライブラリの 1 つの利点は,すべての実行イメージをリンクした後にライブラリを変更できるため,ライブラリのバグを修正できることです。 この機能は,製品の開発期間中に非常に役立ちます。

しかし,製品出荷サイクルでは,通常,開発および修正したシェアード・ライブラリとアプリケーションは,次のリリースまで変更されません。 その場合には,クイックスタートを活用することができます。 これは,ユーザのプログラムとライブラリにあるすべてのシンボルに,あらかじめアドレスを設定する方法です。

アプリケーションをクイックスタートさせるための特別なリンク・オプションは必要ありませんが,満たさなければならない一連の条件があります。 クイックスタートできない場合でもオブジェクトを実行することはできますが,スタートアップ時間は長くなります。

リンカが共用オブジェクト,すなわちシェアード・ライブラリまたはシェアード・ライブラリを使用するメイン実行可能ファイルを作成する場合には,オブジェクトのテキストおよびデータ部分にアドレスを割り当てます。 このアドレスが,いわゆるクイックスタート・アドレスと呼ばれているものです。 オブジェクトがクイックスタート・アドレスでロードされるかのように,リンカは事前にすべての動的再配置を実行します。

依存するすべてのオブジェクトは,クイックスタート用アドレスにあるとみなされます。 元のオブジェクトから依存先のオブジェクトを参照すると,それに応じて依存先のオブジェクトのアドレスが設定されます。

クイックスタートを使用するためには,オブジェクトは次の条件を満たしていなければなりません。

オペレーティング・システムは,これらの条件を,チェックサムとタイムスタンプによって確認します。

ライブラリを作成する場合,それらにはクイックスタート・アドレスが与えられます。 クイックスタートの制約条件を満たすためには,アプリケーションが使用するすべてのライブラリのクイックスタート・アドレスをそれぞれ一意な値に設定する必要があります。 アプリケーション側でアドレスの心配をするよりも,作成した各シェアード・ライブラリに一意なクイックスタート・アドレスを割り当て,すべてのオブジェクトをクイックスタート・アドレスでロードできるようにしてください。

リンカは,ライブラリ作成時にクイックスタート・アドレスが登録される so_locations データベースを保守します。 新しいライブラリに対してクイックスタート・アドレスを選択する際,リンカは,すでに so_locations ファイルに存在するアドレスはクイックスタート・アドレスとして使用しません。

省略時の設定では,-update_registry ./so_locations オプションが選択されたものとして,ld が実行されます。 この結果,作成したライブラリのディレクトリ内の so_locations ファイルが,必要に応じて更新されるかまたは作成されます。

ライブラリがユーザのシステムのシェアード・ライブラリと競合しないよう,次のコマンドを入力してください。

% cd <directory_of_build>
% cp /usr/shlib/so_locations .
% chmod +w so_locations
 

ここまでの手順により,ライブラリを作成することができます。 複数のディレクトリにライブラリを作成した場合には,ld コマンドを -update_registry オプションとともに使用して,共通の so_locations ファイルの位置を明示的に指定します。 たとえば,次のように入力します。

% ld -shared -update_registry /common/directory/so_locations ...
 

システムのすべてのユーザのために,グローバルにシェアード・ライブラリをインストールする場合には,システム全体で使用する so_locations ファイルを更新します。 shared_library.so に実際のシェアード・ライブラリを指定して,ルート・アカウントで次のコマンドを入力します。

# cp shared_library.so /usr/shlib
# mv /usr/shlib/so_locations /usr/shlib/so_locations.old
# cp so_locations /usr/shlib
 

複数の人がシェアード・ライブラリを作成している場合には,他の共用データベースと同様に,共通の so_locations ファイルを管理する必要があります。 任意のプロセスで使用される各シェアード・ライブラリには,ファイルに一意のクイックスタート・アドレスを指定しなければなりません。 リンカがメイン実行可能ファイルに割り当てる省略時の開始アドレスの範囲は,共用オブジェクトに対して作成するクイックスタート・アドレスとは競合しません。 1 つのメイン実行可能ファイルだけがプロセスにロードされるので,メインの実行可能ファイルとその共用オブジェクトの間でアドレスの競合は起こりません。

既存のシェアード・ライブラリを使用しているだけで,ユーザ独自のライブラリを使用しない場合には,特に何もする必要はありません。 ライブラリが前に説明した条件に合っている限り,ライブラリ自身をクイックスタートしない場合を除いて,ユーザのプログラムはクイックスタートされます。 オペレーティング・システムに付属するほとんどのライブラリは,クイックスタートするようになっています。

新規にシェアード・ライブラリを作成している場合には,すでに説明しているように,最初に so_locations ファイルをコピーしなければなりません。 次に,so_locations ファイルを使用して,すべてのシェアード・ライブラリをボトムアップ式の順番で作成しなければなりません。 依存するすべてのライブラリをリンク行に指定してください。 すべてのライブラリを作成した後に,ユーザのアプリケーションを作成することができます。

4.7.1    オブジェクトのクイックスタートの確認

アプリケーションの実行可能プログラムがクイックスタートであるかどうかを調べるには,_RLD_ARGS 環境変数に -quickstart_only を設定して,プログラムを実行します。 たとえば,次のように設定します。

% setenv _RLD_ARGS -quickstart_only
% foo
 

クイックスタートでない場合には,次のような出力になります。

21887:foo: /sbin/loader: Fatal Error: NON-QUICKSTART detected \
  -- QUICKSTART must be enforced
 

プログラムが正常に終了すればクイックスタートです。 ロード・エラー・メッセージが表示された場合には,そのプログラムはクイックスタートではありません。

4.7.2    手動によるクイックスタート問題の解決

実行可能プログラムがクイックスタートでない原因を判断するには,4.7.3 項で説明する fixso ユーティリティを使用するか,あるいは次の条件を調べてみてください。 fixso を使用すると簡単ですが,ここで説明する手順を理解しておくことも役に立ちます。

  1. 実行可能プログラムはクイックスタート可能でなければならない。

    動的ヘッダのクイックスタート・オプションを調べてください。 クイックスタート・オプションの値は 0x00000001 です。

    % odump -D foo | grep FLAGS
     
    

    クイックスタートでない場合の出力は,次のようになります。

      FLAGS: 0x00000000
     
    

    クイックスタートの場合の出力は,次のようになります。

      FLAGS: 0x00000001
     
    

    クイックスタート・オプションが設定されていない場合には,次のことが考えられます。

  2. 実行可能プログラムの依存するライブラリはクイックスタートでなければならない。

    実行可能プログラムの依存するライブラリのリストを表示するには,次のコマンドを実行します。

    %odump -Dl foo
     
    

    クイックスタートの場合の出力は,次のようになります。

                     ***LIBRARY LIST SECTION***
      Name             Time-Stamp        CheckSum   Flags Version
    foo:
      libX11.so    Sep 17 00:51:19 1993 0x78c81c78  NONE
      libc.so      Sep 16 22:29:50 1993 0xba22309c  NONE osf.1
      libdnet_stub.so Sep 16 22:56:51 1993 0x1d568a0c  NONE osf.1
     
    

    各依存ライブラリの動的ヘッダのクイックスタート・フラグを調べます。

    % cd /usr/shlib
    % odump -D libX11.so libc.so libdnet_stub.so | grep FLAGS
     
    

    クイックスタートの場合には,次のような出力になります。

      FLAGS: 0x00000001
      FLAGS: 0x00000001
      FLAGS: 0x00000001
     
    

    依存ライブラリのいずれかがクイックスタートできないとき,シェアード・ライブラリをユーザが作成し直せる場合には,手順 1 で説明した方法で解決してください。

  3. すべての依存ライブラリのタイムスタンプとチェックサム情報が一致していなければならない。

    手順 2 の依存ライブラリ・リストには,foo の各依存ライブラリのタイムスタンプとチェックサムのフィールドに期待値が表示されています。 これらの値と,各ライブラリの現在の値を照合するには,次のようにします。

    % cd /usr/shlib
    % odump -D libX11.so libc.so libdnet_stub.so | \
    grep TIME_STAMP
    

    クイックスタートの場合は,次のような出力になります。

      TIME_STAMP: (0x2c994247) Fri Sep 17 00:51:19 1993
      TIME_STAMP: (0x2c99211e) Thu Sep 16 22:29:50 1993
      TIME_STAMP: (0x2c992773) Thu Sep 16 22:56:51 1993
     
    

    % odump -D libX11.so libc.so libdnet_stub.so | grep CHECKSUM
     
    

    クイックスタートの場合には,次のような出力になります。

      ICHECKSUM: 0x78c81c78
      ICHECKSUM: 0xba22309c
      ICHECKSUM: 0x1d568a0c
     
    

    どちらかのテストで,タイムスタンプまたはチェックサムの不一致があった場合には,プログラムを再リンクすることにより問題は解決されます。

    バージョン・フィールドを使用すると,実行時に正しいライブラリがロードされたことを確認することができます。 依存ライブラリのバージョンを調べるには,次の例に示すように odump コマンドを使用します。

    % odump -D libX11.so | grep IVERSION
    % odump -D libc.so | grep IVERSION
      IVERSION: osf.1
    % odump -D libdnet_stub.so | grep IVERSION
      IVERSION: osf.1
     
    

    依存情報のエントリが空白の場合には,一致する IVERSION エントリはありません。 また,特殊なバージョン _null に一致するエントリもありません。

    バージョンの不一致が見つかった場合には,通常は,依存リストのバージョン識別子を追加するか,または _null をパス /usr/shlib に追加することによって,シェアード・ライブラリの正しいバージョンを見つけることができます。

  4. 実行可能プログラムの各依存ライブラリも,一致するタイムスタンプとチェックサム情報を示す依存リストを含んでいなければならない。

    実行可能プログラムの依存リストに表示されている各シェアード・ライブラリに対して,手順 3 を繰り返して実行します。

    % odump -Dl libX11.so
     
    

    クイックスタートの場合には,次のような出力になります。

                      ***LIBRARY LIST SECTION***
      Name             Time-Stamp        CheckSum   Flags Version
    libX11.so:
      libdnet_stub.so Sep 16 22:56:51 1993 0x1d568a0c  NONE osf.1
      libc.so      Sep 16 22:29:50 1993 0xba22309c  NONE osf.1
    % odump -D libdnet_stub.so libc.so | grep TIME_STAMP
      TIME_STAMP: (0x2c992773) Thu Sep 16 22:56:51 1993
      TIME_STAMP: (0x2c99211e) Thu Sep 16 22:29:50 1993
    % odump -D libdnet_stub.so libc.so | grep CHECKSUM
      ICHECKSUM: 0x1d568a0c
      ICHECKSUM: 0xba22309c
     
    

    タイムスタンプまたはチェックサム情報が一致していない場合には,シェアード・ライブラリを再作成して問題を解決しなければなりません。 シェアード・ライブラリを再作成すると,タイムスタンプが変更され,場合によってはチェックサムも変更されます。 依存ライブラリをボトムアップ式に再作成してから,実行可能プログラムやシェアード・ライブラリを再作成します。

4.7.3    fixso ユーティリティによるクイックスタート問題の解決

fixso ユーティリティは,タイムスタンプおよびチェックサムの不一致が原因で発生するクイックスタートの問題を識別し修復します。 このユーティリティは,プログラムが依存するシェアード・ライブラリと同様に,プログラム自体も修復できます。 ただし,シンボルの変更が必要となるようなプログラムの修復はできません。

fixso ユーティリティは,次に示す制限に該当するプログラムあるいはシェアード・ライブラリは修復できません。

fixso ユーティリティを使用すると,次の例に示すような形でクイックスタート問題を識別することができます。

% fixso -n hello.so
fixso: Warning: found '/usr/shlib/libc.so' (0x2d93b353) which does
       not match timestamp 0x2d6ae076 in liblist of hello.so, will fix
fixso: Warning: found '/usr/shlib/libc.so' (0xc777ff16) which does
       not match checksum 0x70e62eeb in liblist of hello.so, will fix
 

-n オプションを指定すると出力ファイルの生成を行いません。 この例では,不一致が報告されていますが問題の修復は行われていません。 次の例では,fixso を使ってクイックスタート問題を修復する方法を説明します。

% fixso -o ./fixed/main main
fixso: Warning: found '/usr/shlib/libc.so' (0x2d93b353) which does
       not match timestamp 0x2d7149c9 in liblist of main, will fix
% chmod +x fixed/main
 

-o オプションは出力ファイルを指定します。 出力ファイル名が指定されない場合は a.out が使用されます。 fixso が作成する出力ファイルは,ファイル保護コードが実行可能ではないことに注意してください。 chmod コマンドで出力ファイルの保護コードを変更しています。 この変更処理は,実行可能プログラムに対してのみ必要になります。 fixso を使用してシェアード・ライブラリを修復する場合は省略できます。

プログラムあるいはシェアード・ライブラリの修復が不要な場合,fixso は,次の例に示すように指摘します。

% fixso -n /bin/ls
no fixup needed for /bin/ls
 

4.8    シェアード・ライブラリとリンクしているプログラムのデバッグ

シェアード・ライブラリを使用しているプログラムをデバッグする方法は,アーカイブ・ライブラリを使用しているプログラムをデバッグする方法と基本的には同じです。

dbx デバッガの listobj コマンドは,実行プログラムの名前とデバッガが認識できるすべてのシェアード・ライブラリを表示します。 dbx についての詳細は,第 5 章を参照してください。

4.9    シェアード・ライブラリの実行時のロード

プログラムからシェアード・ライブラリをロードする場合があります。 この節では,2 つの短い C プログラムと makefile を例に,実行時にシェアード・ライブラリをロードする方法を示します。

簡単なメッセージを表示する C プログラムのソース・ファイルの例 (pr.c) を,次に示します。

printmsg()
   {
     printf("Hello world from printmsg!\n");
   }
 

シンボルを定義し,dlopen 関数を使用する方法を示す C プログラムのソース・ファイルの例 (used1.c) を,次に示します。

#include <stdio.h>
#include <dlfcn.h>
 
/* dl* ルーチンからのエラーはすべて NULL で戻される。*/
#define BAD(x)      ((x) == NULL)
 
main(int argc, char *argv[])
{
    void *handle;
    void (*fp)();
 
    /* ./ 接頭語を使用すると,dlpen は現在の
     * ディレクトリを探索する。それ以外の場合には,
     * LD_LIBRARY_PATH などを使用する。
     */
    handle = dlopen("./pr.so", RTLD_LAZY);
    if (!BAD(handle)) {
     fp = dlsym(handle, "printmsg");
     if (!BAD(fp)) {
         /*
          * ここで,探索していた関数が呼び出される。
          */
         (*fp)();
     }
     else {
          perror("dlsym");
          fprintf(stderr, "%s\n", dlerror());
      }
     }
     else {
      perror("dlopen");
      fprintf(stderr, "%s\n", dlerror());
     }
     dlclose(handle);
}
 

次に,pr.opr.soso_locationsusedl.o を生成する makefile を示します。

# これは例を検証する makefile である。
 
all: runit
 
runit:	usedl pr.so
     ./usedl
 
usedl:	usedl.c
     $(CC) -o usedl usedl.c
 
pr.so:	pr.o
     $(LD) -o pr.so -shared pr.o -lc
 

4.10    シェアード・ライブラリ・ファイルの保護

シェアード・ライブラリに共用のためのメカニズムが使用されているため,通常のファイル・システムのための保護機能では,不正な読み取りを防ぐことができません。 たとえば,シェアード・ライブラリがプログラムの中で使用されている場合,そのライブラリのテキスト部分は,次に示すような状況においても他のプロセスから読み取ることができます。

この場合,テキスト部分だけが共用され,データ・セグメントは共用されません。

必要以外の共用を行わないために,保護する必要のあるシェアード・ライブラリは -T オプションや -D オプションを使用してリンクし,データ・セクションを次のセクションと同じ 8 MB のセグメントに格納してください。 たとえば,次の例のようなコマンドを入力してください。

% ld -shared -o libfoo.so -T 30000000000 \
-D 30000400000 object_files
 

さらに,マップされたアドレスが mmap を使用している他のファイルと同じメモリ・セグメントを参照している限り,PROT_WRITE オプションを使用せずに mmap システム・コールを使用しているどのようなファイルにもセグメントの共用が発生する可能性があります。

mmap を使用して厳重に保護されている可能性のあるファイルを検査するプログラムを使用すると,mmap の前またはその途中セグメントに書き込み可能なページを挿入して,セグメントの共用を防ぐことができます。 シェアード・ライブラリを最も簡単に保護するには,保護機能の実行の際に使用可能になる PROT_WRITE が使用されているファイルで mmap システム・コールを使用し,mprotect システム・コールを使用してマップされたメモリを読み取りのみ可能にしてください。 別の方法として,すべてのセグメント化を使用不能にし,権限のない共用を防ぐには,構成ファイルに次の行を入力します。

segmentation 0
 

4.11    シェアード・ライブラリのバージョン管理

シェアード・ライブラリを使用することの利点の 1 つは,シェアード・ライブラリが変更されても,そのライブラリとリンクしているプログラムを再作成する必要がないということです。 変更されたライブラリがインストールされると,アプリケーションは古いライブラリを使用していたときと同様に新しいライブラリを使用して動作します。

注意

新しいバージョンのシェアード・ライブラリがインストールされると,古いバージョンのシェアード・ライブラリを使用する既存のアプリケーションは,追加アドレスの決定が必要になるため,ロード時間が長くなる場合があります。 アプリケーションを新しいライブラリにリンクし直すと,このようなアドレスの決定を避けて,ロード時間を短縮することができます。

4.11.1    バイナリ非互換修正

まれに,シェアード・ライブラリを変更すると,そのライブラリと変更前にリンクしていたアプリケーションとの互換性がなくなることがあります。 この種の変更をバイナリ非互換といいます。 シェアード・ライブラリの新しいバージョンでバイナリ非互換が起こっても,古いバージョンのライブラリに依存するアプリケーションは,必ずしもエラーになる (つまり,ライブラリの下位互換性に違反する) わけではありません。 システムではシェアード・ライブラリのバージョン管理を行うことによって,ライブラリでバイナリ非互換が起こった場合に,シェアード・ライブラリの下位互換性を維持する処置がとれるようにしています。

シェアード・ライブラリで非互換を起こす修正には,次のような種類があります。

もちろん,これだけがバイナリ非互換を引き起こす変更のすべてではありません。 シェアード・ライブラリの開発者は常識を働かせて,どのような変更を行うと,変更前のライブラリとリンクしていたアプリケーションに障害が生じるかを判断してください。

4.11.2    シェアード・ライブラリのバージョン管理

非互換変更の影響を受けたシェアード・ライブラリの下位互換性は,ライブラリの複数バージョンを使用することにより維持できます。 各シェアード・ライブラリはバージョン識別子によってマークされます。 ライブラリの新しいバージョンを省略時のディレクトリにインストールし,そのライブラリのバージョン識別子と一致する名前を持つ,古いバイナリ互換バージョンをサブディレクトリにインストールします。

たとえば,libc.so に非互換の変更が行われた場合,新しいライブラリ (/usr/shlib/libc.so) は,変更前のライブラリのインスタンス (/usr/shlib/osf.1/libc.so) を伴っていなければなりません。

この例では,libc.so の古いバイナリ互換バージョンは osf.1 バージョンです。 変更が適用されると,新しい libc.so が新しいバージョン識別子を使用して作成されます。 シェアード・ライブラリのバージョン識別子は,そのライブラリを使用するプログラムのシェアード・ライブラリ従属レコードにリストされているため,ローダはアプリケーションで必要とするシェアード・ライブラリのバージョンを識別できます (4.11.6 項を参照)。

前述の例では,バイナリ非互換変更の前に libc.so を使用して作成したプログラムは,ライブラリの osf.1 バージョンを必要とします。 /usr/shlib/libc.so のバージョンはプログラムのシェアード・ライブラリ従属レコードにリストされているバージョン識別子と一致しないため,ローダは /usr/shlib/osf.1 で一致するバージョンを探します。

非互換変更後に作成されるアプリケーションは,/usr/shlib/libc.so を使用して作成され,ライブラリの新しいバージョンに依存します。 ローダは,別のバイナリ非互換変更が起こるまで,/usr/shlib/libc.so を使用してこれらのアプリケーションをロードします。

表 4-1 は,シェアード・ライブラリのバージョン管理を有効にするためのリンカ・オプションの説明です。

表 4-1:  シェアード・ライブラリのバージョンを管理するリンカ・オプション

オプション 説明
-set_version version-string

シェアード・ライブラリに関連するバージョン識別子を設定する。 文字列 version-string は,単一のバージョン識別子またはコロンで区切ったバージョン識別子のリストである。 バージョン識別子の名前に関する制約はないが,UNIX のディレクトリ命名規則に従うのがよい。

シェアード・ライブラリがこのオプションを使用して作成されると,そのライブラリに対して作成されたプログラムは,指定されたバージョン,またはバージョン識別子のリストが指定されている場合には,そのリストに指定されている一番右端のバージョンの従属が記録される。 シェアード・ライブラリがバージョン識別子のリストを使用して作成されると,実行時ローダは,リストされている任意のバージョンに関するシェアード・ライブラリの従属を持つプログラムはどれでも実行できるようにする。

このオプションはシェアード・ライブラリの作成時のみに (-shared を指定して) 使用できる。

-exact_version

ld コマンドで作成された動的オブジェクトにオプションを設定する。 このコマンドを使用すると,実行時ローダは確実に,オブジェクトが実行時に使用するシェアード・ライブラリが,リンク時に使用されたシェアード・ライブラリと一致するようにさせる。

このオプションは,動的実行可能ファイル (-call_shared を指定) またはシェアード・ライブラリ (-shared を指定) の作成時に使用される。 このオプションを使用した場合には,シェアード・ライブラリの従属のより厳密なテストを行う必要がある。 シェアード・ライブラリのバージョンが一致することをテストするだけでなく,タイムスタンプとチェックサムも,リンク時におけるシェアード・ライブラリ従属レコードに記録されているタイムスタンプおよびチェックサムと一致することをテストしなければならない。

odump コマンドを使用すると,シェアード・ライブラリのバージョン文字列を確認することができます。 この文字列は,そのライブラリを作成した ld コマンドの -set_version  version-string オプションで設定されるものです。 次のように入力します。

% odump -D library-name

IVERSION フィールドに表示される値が,ライブラリの作成時に指定されたバージョン文字列です。 -set_version オプションを指定しないでライブラリを作成した場合には,IVERSION フィールドは表示されません。 このようなシェアード・ライブラリは,バージョン識別子として _null が指定された場合と同様に扱われます。

ld が共用オブジェクトをリンクするときには,各シェアード・ライブラリの従属のバージョンを記録します。 コロンで区切ったリストの一番右端にあるバージョン識別子だけが記録されます。 任意の共用されている実行可能ファイルまたはライブラリのこれらの依存関係を確認するには,次のコマンドを使用します。

% odump -Dl shared-object-name

4.11.3    メジャーおよびマイナー・バージョン識別子

Tru64 UNIX では,シェアード・ライブラリのメジャー・バージョンとマイナー・バージョンを区別しません。

Tru64 UNIX のシェアード・ライブラリは,コロンで区切ったバージョン識別子のリストを使用することによって,通常はマイナー・バージョンを使用して行うバージョン管理機能を提供しています。

次に示すライブラリ・バージョンのシーケンスは,シェアード・ライブラリの互換性に影響を与えることなく,修正版用の識別子をシェアード・ライブラリのバージョン・リストに追加する方法を示しています。

シェアード・ライブラリ バージョン
libminor.so 3.0
libminor.so 3.1:3.0
libminor.so 3.2:3.1:3.0

libminor.so の新しいリリースはそれぞれ,バージョン・リストの先頭に新しい識別子を追加します。 この新しい識別子により,前のバージョンと最新バージョンを区別します。 libminor.so の任意のバージョンとリンクする実行可能ファイルは,必須バージョンとして 3.0 を記録するため,互換ライブラリと区別がつきません。 追加のバージョン識別子だけが情報を提供します。

次のライブラリ・バージョンのシーケンスは,下位互換のシェアード・ライブラリの使用を制限する方法を示しています。

シェアード・ライブラリ バージョン
libminor2.so 3.0
libminor2.so 3.0:3.1
libminor2.so 3.0:3.1:3.2

この例では,libminor2.so の古いバージョンとリンクされるプログラムは,ライブラリの新しいバージョンで実行できますが,libminor2.so の新しいバージョンとリンクされるプログラムは,古いバージョンでは実行できません。

4.11.4    シェアード・ライブラリの完全バージョンと部分バージョン

シェアード・ライブラリのバイナリ互換バージョンは,2 つの方法でインプリメントできます。 つまり,完全で独立したオブジェクトと,直接または間接的に完全で独立したオブジェクトに依存する部分オブジェクトとしてインプリメントできます。 完全に重複したシェアード・ライブラリは部分バージョンより多くのディスク・スペースを必要としますが,従属処理が単純で,使用するスワップ領域が少なくてすみます。 使用するディスク・スペースを削減できることが,シェアード・ライブラリの部分バージョンの唯一の利点です。

部分シェアード・ライブラリは,ライブラリの新しいバージョンでバイナリ非互換変更が行われる前にリンクされたアプリケーションに対して,下位互換を維持するために最低限必要なモジュールのサブセットを含みます。 これは,ライブラリ・モジュールの完全なセットを持つ同じライブラリの 1 つまたは複数の以前のバージョンとリンクしています。 このように,シェアード・ライブラリの複数のバージョンを連結すると,シェアード・ライブラリの任意のインスタンスは,通常はライブラリでエクスポートされるすべてのシンボルを,間接的に提供できるようになります。

たとえば,libxyz.so のバージョン osf.1 には,モジュール x.oy.oz.o が含まれています。 これは,次のコマンドを使用して作成され,インストールされました。

% ld -shared -o libxyz.so -set_version osf.1 \
    x.o y.o z.o -lc
% mv libxyz.so /usr/shlib/libxyz.so
 

将来,libxyz.so において,モジュール z.o だけに影響を及ぼす非互換変更が必要になった場合には,osf.2 と呼ばれる新しいバージョンと,osf.1 と呼ばれる部分バージョンは,次のように作成することができます。

% ld -shared -o libxyz.so -set_version osf.2 x.o \
    y.o new_z.o -lc
% mv libxyz.so /usr/shlib/libxyz.so
% ld -shared -o libxyz.so -set_version osf.1 z.o \
    -lxyz -lc
% mv libxyz.so /usr/shlib/osf.1/libxyz.so
 

4.11.5    シェアード・ライブラリの複数バージョンとのリンク

一般に,アプリケーションはシェアード・ライブラリの最新バージョンとリンクされます。 しかし,アプリケーションやシェアード・ライブラリを,シェアード・ライブラリの古い,バイナリ互換バージョンとリンクしたいことがあります。 そのような場合には,ld コマンドの -L オプションを使用して,アプリケーションが使用するシェアード・ライブラリの古いバージョンを識別します。

アプリケーションを同じシェアード・ライブラリの複数のバージョンとリンクすると,リンカは警告メッセージを出します。 アプリケーションまたはシェアード・ライブラリの複数バージョンの従属は,実行のためにロードされるまで通知されない場合があります。

ld コマンドは,省略時には,リンクするように指示されているライブラリのみの複数バージョンの従属を調べます。 考えられるすべての複数バージョンの従属を識別するには,ld コマンドの -transitive_link オプションを使用して,リンクのステップに間接シェアード・ライブラリの従属を含めます。

アプリケーションを部分シェアード・ライブラリとリンクする場合には,部分シェアード・ライブラリのインプリメンテーションで生じた複数バージョンの従属を注意深く区別しなければなりません。 リンカは,受け入れ可能な複数バージョンの従属と受け入れ不可能なものの区別ができない場合,複数バージョンの警告メッセージを表示します。

場合によっては,実行時にシェアード・ライブラリの複数バージョンを使用しないアプリケーションに対し,リンク時に複数バージョンの従属が通知されます。 図 4-2 および次の表に示すライブラリと従属について考えてみてください。

図 4-2:  シェアード・ライブラリの複数バージョンとのリンク

ライブラリ バージョン 従属 依存するバージョン
libA.so v1 libcommon.so v1
libB.so v2 libcommon.so v2
libcommon.so v1:v2 -- --

この表で,libA.so は,右端のバージョン識別子に v1 を含む libcommon.so にリンクされています。 libB.so は,右端のバージョン識別子に v2 を含む libcommon.so にリンクされています。 この表に示す libcommon.so は,バージョン文字列に v1 と v2 の両方を含むため,libA.so および libB.so の従属はともに libcommon.so という 1 つのインスタンスで満足できます。

a.out がリンクされるとき,libA.so および libB.so だけがリンクのコマンド行に指定されます。 ただし,ldlibA.so および libB.so の従属を調べ,libcommon.so の考えられる複数バージョンの従属を承認して警告メッセージを出します。 a.outlibcommon.so にもリンクすると,この間違った警告を回避することができます。

4.11.6    ロード時におけるバージョン・チェック

ローダは,シェアード・ライブラリがサポートするバージョン・リストと,シェアード・ライブラリの従属レコードに記録されているバージョンの照合を実行します。 共用オブジェクトがリンクのコマンド行で -exact_match オプションを使用してリンクされている場合には,ローダはシェアード・ライブラリのタイムスタンプとチェックサムも従属レコードに記録されている値と比較します。

ローダはバージョン照合検査が失敗したシェアード・ライブラリでマップした後,RPATHLD_LIBRARY_PATH,または省略時の探索パスに指定されている他のディレクトリの探索を続行して,シェアード・ライブラリの正しいバージョンを見つけようとします。

これらのディレクトリをすべて探索しても見つからない場合には,ローダは,従属に記録されているバージョン文字列を,最初に一致しないライブラリのバージョンが見つかったディレクトリ・パスに付加して,一致するバージョンを探索しようとします。

たとえば,シェアード・ライブラリ libfoo.so はバージョン osf.2 を使用してディレクトリ /usr/local/lib にロードされますが,このライブラリの従属はバージョン osf.1 を必要とします。 ローダは組み合わせた次のパスを使用して,ライブラリの正しいバージョンの探索を試みます。

/usr/local/lib/osf.1/libfoo.so
 

この組み合わせたパスでも正しいライブラリの探索に失敗した場合,あるいは,省略時の探索ディレクトリまたはユーザ指定の探索ディレクトリでもライブラリのバージョンが探索できなかった場合,ローダは,必須のバージョン文字列を標準システムのシェアード・ライブラリのディレクトリ (/usr/shlib) に付加して,最後の探索を試みます。

前述の例を使用すると,libfoo.so を探索するローダの最後の試みでは,次のような組み合わせたパスを使用します。

/usr/shlib/osf.1/libfoo.so
 

ローダがシェアード・ライブラリの一致するバージョンを見つけられない場合,ロードは打ち切られて,探索できなかった従属とシェアード・ライブラリのバージョンを示す詳細なエラー・メッセージが報告されます。

setuid 関数を使用してインストールされなかったプログラムのバージョン・チェックは,次の C シェルの例に示すように,ローダの環境変数を設定することにより,禁止することができます。

% setenv _RLD_ARGS -ignore_all_versions
 

次の例に示すように,特定のシェアード・ライブラリに対するバージョン・チェックを禁止することも可能です。

% setenv _RLD_ARGS -ignore_version libDXm.so
 

4.11.7    ロード時における複数バージョンのチェック

リンカと同様,ローダもシェアード・ライブラリの複数バージョンの有効な使用と無効な使用を区別しなければなりません。

次の図は,複数従属エラーになる共用オブジェクトの従属図式を示しています。 バージョン識別子はカッコ内に示しています。

図 4-3 では,アプリケーションは,基本システムの非互換バージョンで作成された 2 つのレイヤード・プロダクトを使用しています。

図 4-3:  共用オブジェクト間の無効な複数バージョンの従属: 例 1

図 4-4 では,アプリケーションは,基本システムの非互換バージョンを使用して作成されたレイヤード・プロダクトとリンクしています。

図 4-4:  共用オブジェクト間の無効な複数バージョンの従属: 例 2

図 4-5 では,アプリケーションは,部分シェアード・ライブラリとしてインプリメントされた下位互換の不完全なライブラリとリンクしています。

図 4-5:  共用オブジェクト間の無効な複数バージョンの従属: 例 3

次の例は,シェアード・ライブラリの複数バージョンの有効な使用を示しています。

図 4-6 では,アプリケーションは,部分シェアード・ライブラリとしてインプリメントされた下位互換のライブラリを使用しています。

図 4-6:  シェアード・ライブラリの複数バージョンの有効な使用: 例 1

図 4-7 では,アプリケーションは,2 つの下位互換のライブラリを使用しています。 そのうちの 1 つは,もう一方に依存しています。

図 4-7:  シェアード・ライブラリの複数バージョンの有効な使用: 例 2

4.12    シンボル割り当て

ローダによるシンボル解決の方法には,即時割り当ておよび遅延割り当ての 2 つの方法があります。 即時割り当ての場合は,実行可能プログラムあるいはシェアード・ライブラリがロードされるときにシンボルが解決されます。 遅延割り当ての場合,テキスト・シンボルは実行時に解決されます。 遅延テキスト・シンボルは,プログラムで最初に参照される際に解決されます。

省略時の設定では,プログラムは遅延割り当てでロードされます。 LD_BIND_NOW 環境変数にヌル以外の値に設定すると,その後のプログラムの実行は即時割り当てによって行われます。

即時割り当ては,解決できないシンボルを識別するのに便利です。 遅延割り当ての場合は,その部分のコードが実行されるまで,解決できないシンボルを検出できません。

また,即時割り当てを使用するとシンボル解決のオーバヘッドを軽減できます。

4.13    シェアード・ライブラリの制約事項

シェアード・ライブラリを使用する際には,次の制約事項を満たす必要があります。