リンカーとライブラリ

付録 C 動的ストリングトークンによる依存関係の確立

動的オブジェクトは、明示的に、またはフィルタを通して依存関係を確立できます。それぞれの仕組みは「実行パス」で拡張できます。「実行パス」は実行時リンカーに、要求された依存関係を検索させ、読み込ませる指示を送ります。フィルタ、依存関係、および「実行パス」の情報を記録するストリング名は、次の予約された動的ストリングトークンによって拡張できます。

以降のセクションでは、これらのトークンの使用方法について具体的な例を示します。

ハードウェア機能固有の共有オブジェクト

動的トークン $HWCAP を使用して、ハードウェア機能固有の共有オブジェクトがあるディレクトリを指定することができます。このトークンは、フィルタまたは依存関係に対して使用できます。このトークンは複数のオブジェクトに拡張できるので、依存関係と使用する場合も管理できます。dlopen(3C) で取得された依存関係は、RTLD_FIRST モードでこのトークンを使用できます。このトークンを使用する明示的な依存関係は、最初に見つかった適切な依存関係を読み込みます。

パス名の指定は、$HWCAP トークンで終わるフルパス名で構成する必要があります。$HWCAP トークンで指定されたディレクトリ内の共有オブジェクトは、実行時に検査されます。これらのオブジェクトは、ハードウェア機能の要件を示す必要があります。「ハードウェアとソフトウェア機能の特定」を参照してください。各オブジェクトは、プロセスで使用可能なハードウェア機能に対して検証されます。プロセスと使用できるオブジェクトは、ハードウェア機能値の降順で保存されます。これらのソートされたフィルティーは、フィルタ内で定義されたシンボルを解決するために使用されます。

ハードウェア機能ディレクトリ内のフィルティーには、命名に関する制限はありません。次の例で、ハードウェア機能フィルティーにアクセスするために補助フィルタ libfoo.so.1 をどのように設計するかを示します。


$ LD_OPTIONS='-f /opt/ISV/lib/hwcap/$HWCAP' \
cc -o libfoo.so.1 -G -K pic -h libfoo.so.1 -R. foo.c
$ dump -Lv libfoo.so.1 | egrep "SONAME|AUXILIARY"
  [1]    SONAME    libfoo.so.1
  [2]    AUXILIARY /opt/ISV/lib/hwcap/$HWCAP
$ elfdump -H /opt/ISV/lib/hwcap/*

/opt/ISV/lib/hwcap/filtee.so.3:

Hardware/Software Capabilities Section:  .SUNW_cap
     index  tag               value
       [0]  CA_SUNW_HW_1     0x1000  [ SSE2 ]

/opt/ISV/lib/hwcap/filtee.so.1:

Hardware/Software Capabilities Section:  .SUNW_cap
     index  tag               value
       [0]  CA_SUNW_HW_1     0x40  [ MMX ]

/opt/ISV/lib/hwcap/filtee.so.2:

Hardware/Software Capabilities Section:  .SUNW_cap
     index  tag               value
       [0]  CA_SUNW_HW_1     0x800  [ SSE ]

フィルタ libfoo.so.1MMXSSE 機能を使用できるプラットフォームで処理した場合、次のフィルティー検索順序が採用されます。


$ cc -o prog prog.c -R. -lfoo
$ LD_DEBUG=symbols prog
.....
01233: symbol=foo;  lookup in file=libfoo.so.1  [ ELF ]
01233: symbol=foo;  lookup in file=hwcap/filtee.so.2  [ ELF ]
01233: symbol=foo;  lookup in file=hwcap/filtee.so.1  [ ELF ]
.....

なお、filtee.so.2 の機能値は filtee.so.1 の機能値より大きくなります。SSE2 機能を使用できないので、filtee.so.3 がシンボル検索に含まれる可能性はありません。

「フィルティー」検索の縮小

フィルタ内で $HWCAP を使用すると、1 つまたは複数の「フィルティー」が、フィルタ内で定義されたインタフェースの実装を提供できます。

指定された $HWCAP ディレクトリ内のすべての共有オブジェクトは、使用可能性を検証し、プロセスに適したものをソートするために、点検されます。ソートされると、すべてのオブジェクトは使用準備のため読み込まれます。

リンカーの -z endfiltee オプションを使用して「フィルティー」を作成して、これが使用可能な最後の「フィルティー」であることを示します。このオプションで特定されたフィルティーは、そのフィルタのフィルティーのソートリストを終了します。このフィルティーをフィルタに対して読み込んだ後は、いかなるオブジェクトもソートされません。前の例で filter.so.2 フィルティーに -z endfiltee のタグが付けられている場合、フィルティー検索は次のようになります。


$ LD_DEBUG=symbols prog
.....
01424: symbol=foo;  lookup in file=libfoo.so.1  [ ELF ]
01424: symbol=foo;  lookup in file=hwcap/filtee.so.2  [ ELF ]
.....

命令セット固有の共有オブジェクト

動的トークン $ISALIST は、実行時に展開され、このプラットフォームで実行可能なネイティブの命令セットを反映します。この様子はユーティリティー isalist(1) によって表されます。このトークンは、フィルタ、実行パス定義、および依存関係に対して使用できます。このトークンは複数のオブジェクトに拡張できるので、依存関係と使用する場合も管理できます。dlopen(3C) で取得された依存関係は、RTLD_FIRST モードでこのトークンを使用できます。このトークンを使用する明示的な依存関係は、最初に見つかった適切な依存関係を読み込みます。


注 –

このトークンは廃止されたため、Solaris の今後のリリースで削除される可能性があります。命令セットの拡張を処理するために推奨されるテクニックについては、「ハードウェア機能固有の共有オブジェクト」を参照してください。


$ISALIST トークンに組み込まれたストリング名はすべて、複数の文字列に効率良く複製されます。各文字列には、使用可能な命令セットの 1 つが割り当てられます。

次の例では、命令セット固有の「フィルティー」libfoo.so.1 にアクセスするように補助フィルタ libbar.so.1 を設計する方法を示します。


$ LD_OPTIONS='-f /opt/ISV/lib/$ISALIST/libbar.so.1' \
cc -o libfoo.so.1 -G -K pic -h libfoo.so.1 -R. foo.c
$ dump -Lv libfoo.so.1 | egrep "SONAME|AUXILIARY"
  [1]    SONAME    libfoo.so.1
  [2]    AUXILIARY /opt/ISV/lib/$ISALIST/libbar.so.1

あるいは、代わりに「実行パス」を使用することができます。


$ LD_OPTIONS='-f libbar.so.1' \
cc -o libfoo.so.1 -G -K pic -h libfoo.so.1 -R'/opt/ISV/lib/$ISALIST' foo.c
$ dump -Lv libfoo.so.1 | egrep "RUNPATH|AUXILIARY"
  [1]    RUNPATH   /opt/ISV/lib/$ISALIST
  [2]    AUXILIARY libbar.so.1

どちらの場合でも、実行時リンカーはプラットフォームで使用可能な命令リストを使用して、複数の検索パスを構成します。たとえば、次のアプリケーションは libfoo.so.1 に依存関係があり、SUNW,Ultra-2 上で実行されます。


$ ldd -ls prog
.....
  find object=libbar.so.1; required by ./libfoo.so.1
    search path=/opt/ISV/lib/$ISALIST  (RPATH from file ./libfoo.so.1)
      trying path=/opt/ISV/lib/sparcv9+vis/libbar.so.1
      trying path=/opt/ISV/lib/sparcv9/libbar.so.1
      trying path=/opt/ISV/lib/sparcv8plus+vis/libbar.so.1
      trying path=/opt/ISV/lib/sparcv8plus/libbar.so.1
      trying path=/opt/ISV/lib/sparcv8/libbar.so.1
      trying path=/opt/ISV/lib/sparcv8-fsmuld/libbar.so.1
      trying path=/opt/ISV/lib/sparcv7/libbar.so.1
      trying path=/opt/ISV/lib/sparc/libbar.so.1

また、同じ依存関係を持つアプリケーションは、MMX 設計の Pentium Pro で実行されます。


$ ldd -ls prog
.....
  find object=libbar.so.1; required by ./libfoo.so.1
    search path=/opt/ISV/lib/$ISALIST  (RPATH from file ./libfoo.so.1)
      trying path=/opt/ISV/lib/pentium_pro+mmx/libbar.so.1
      trying path=/opt/ISV/lib/pentium_pro/libbar.so.1
      trying path=/opt/ISV/lib/pentium+mmx/libbar.so.1
      trying path=/opt/ISV/lib/pentium/libbar.so.1
      trying path=/opt/ISV/lib/i486/libbar.so.1
      trying path=/opt/ISV/lib/i386/libbar.so.1
      trying path=/opt/ISV/lib/i86/libbar.so.1

「フィルティー」検索の縮小

フィルタ内で $ISALIST を使用すると、1 つまたは複数の「フィルティー」が、フィルタ内で定義されたインタフェースの実装を提供できます。

フィルタ内でどのようなインタフェースを定義しても、目的のインタフェースを探すために、可能性のある「フィルティー」すべてを徹底的に検索する結果になり得ます。性能が重要となる機能を提供するために「フィルティー」を使用する場合には、徹底的な「フィルティー」の検索は逆効果になるかもしれません。

リンカーの -z endfiltee オプションを使用して「フィルティー」を作成して、これが使用可能な最後の「フィルティー」であることを示します。このオプションによって、該当するフィルタに対してそれ以上の「フィルティー」検索を行わないようにできます。前の SPARC の例で、SPARCV9 フィルティーが存在し、-z endfiltee のタグが付いている場合、フィルティー検索は次のようになります。


$ ldd -ls prog
.....
  find object=libbar.so.1; required by ./libfoo.so.1
    search path=/opt/ISV/lib/$ISALIST  (RPATH from file ./libfoo.so.1)
      trying path=/opt/ISV/lib/sparcv9+vis/libbar.so.1
      trying path=/opt/ISV/lib/sparcv9/libbar.so.1

システム固有の共有オブジェクト

動的トークン $OSNAME$OSREL、および $PLATFORM は実行時に展開されて、システム固有の情報を提供します。これらのトークンは、フィルタ、「実行パス」、または依存関係の定義に利用できます。

$OSNAME は、ユーティリティー uname(1) にオプション -s を付けて実行した場合に表示されるように、オペレーティングシステムの名前を反映して展開されます。$OSREL は、uname -r を実行した場合に表示されるように、オペレーティングシステムのリリースレベルを反映して展開されます。$PLATFORM は、uname -i を実行した場合に表示されるように、基礎としているハードウェア実装を反映して展開されます。

次の例は、プラットフォーム固有の「フィルティー」libfoo.so.1 にアクセスするように補助フィルタ libbar.so.1 を設計する方法を示します。


$ LD_OPTIONS='-f /platform/$PLATFORM/lib/libbar.so.1' \
cc -o libfoo.so.1 -G -K pic -h libfoo.so.1 -R. foo.c
$ dump -Lv libfoo.so.1 | egrep "SONAME|AUXILIARY"
  [1]    SONAME    libfoo.so.1
  [2]    AUXILIARY /platform/$PLATFORM/lib/libbar.so.1

このメカニズムは、共有オブジェクト /lib/libc.so.1 に対してプラットフォーム固有の拡張を提供するために、Solaris OS で使用されます。

関連する依存関係の配置

通常、バンドルされていない製品は、固有の場所にインストールされるように設計されています。この製品は、バイナリ、共有オブジェクト、および関連構成ファイルからなります。たとえば、バンドルされていない製品 ABC は、次の配置をとる場合があります。

図 C–1 バンドルされていない製品の相互依存関係

バンドルされない依存関係の例。

製品が、/opt の元にインストールされるように設計されていると想定します。通常、ユーザーは、PATH に製品バイナリの位置を示す /opt/ABC/bin を追加する必要があります。各バイナリは、バイナリ内に直接記録された「実行パス」を使用して、その依存する相手を探します。アプリケーション abc の場合、この「実行パス」は次のようになります。


$ cc -o abc abc.c -R/opt/ABC/lib -L/opt/ABC/lib -lA
$ dump -Lv abc
  [1]    NEEDED  libA.so.1
  [2]    RUNPATH /opt/ABC/lib

libA.so.1 の依存関係でも同様に、実行パスは次のようになります。


$ cc -o libA.so.1 -G -Kpic A.c -R/opt/ABC/lib -L/opt/ABC/lib -lB
$ dump -Lv libA.so.1
  [1]    NEEDED  libB.so.1
  [2]    RUNPATH /opt/ABC/lib

この依存関係の表現は、製品が推奨されているデフォルト以外のディレクトリにインストールされるまで正常に作動します。

動的トークン $ORIGIN は、オブジェクトが存在するディレクトリに展開されます。このトークンは、フィルタ、「実行パス」、または依存関係の定義に利用できます。この機構を使用すると、バンドルされていないアプリケーションを再定義して、$ORIGIN との相対位置で依存対象の位置を示すことができます。


$ cc -o abc abc.c '-R$ORIGIN/../lib' -L/opt/ABC/lib -lA
$ dump -Lv abc
  [1]    NEEDED  libA.so.1
  [2]    RUNPATH $ORIGIN/../lib

$ORIGIN との関係で libA.so.1 の依存関係を定義することもできます。


$ cc -o libA.so.1 -G -Kpic A.c '-R$ORIGIN' -L/opt/ABC/lib -lB
$ dump -Lv libA.so.1
  [1]    NEEDED  libB.so.1
  [2]    RUNPATH $ORIGIN

この製品が /usr/local/ABC 内にインストールされ、ユーザーの PATH/usr/local/ABC/bin が追加された場合、アプリケーション abc を呼び出すと次のように、パス名検索でその依存関係が探されます。


$ ldd -s abc
.....
  find object=libA.so.1; required by abc
    search path=$ORIGIN/../lib  (RUNPATH/RPATH from file abc) 
      trying path=/usr/local/ABC/lib/libA.so.1
        libA.so.1 =>     /usr/local/ABC/lib/libA.so.1
 
  find object=libB.so.1; required by /usr/local/ABC/lib/libA.so.1
    search path=$ORIGIN  (RUNPATH/RPATH from file /usr/local/ABC/lib/libA.so.1)
      trying path=/usr/local/ABC/lib/libB.so.1
        libB.so.1 =>     /usr/local/ABC/lib/libB.so.1

バンドルされていない製品間の依存関係

依存関係の場所に関するもう 1 つの問題は、バンドルされていない製品同士の依存関係を表現するモデルを、どのようにして確立するかです。

たとえば、バンドルされていない製品 XYZ は製品 ABC に対して依存関係を持つ場合があります。この依存関係を確立するには、ホストパッケージインストールスクリプトを使用します。このスクリプトは ABC 製品のインストール位置へのシンボリックリンクを生成します (次の図を参照)。

図 C–2 バンドルされていない製品の「相互依存関係」

バンドルされていない製品の相互依存関係の例。

XYZ 製品のバイナリおよび共有オブジェクトは、シンボリックリンクを使用して、ABC 製品との依存関係を表現できます。このリンクはその時点で、安定した参照点になります。アプリケーション xyz の場合、この「実行パス」は次のようになります。


$ cc -o xyz xyz.c '-R$ORIGIN/../lib:$ORIGIN/../ABC/lib' \
-L/opt/ABC/lib -lX -lA
$ dump -Lv xyz
  [1]    NEEDED  libX.so.1
  [2]    NEEDED  libA.so.1
  [3]    RUNPATH $ORIGIN/../lib:$ORIGIN/../ABC/lib

libX.so.1 の依存関係でも同様に、この実行パスは次のようになります。


$ cc -o libX.so.1 -G -Kpic X.c '-R$ORIGIN:$ORIGIN/../ABC/lib' \
-L/opt/ABC/lib -lY -lC
$ dump -Lv libX.so.1
  [1]    NEEDED  libY.so.1
  [2]    NEEDED  libC.so.1
  [3]    RUNPATH $ORIGIN:$ORIGIN/../ABC/lib

この製品が /usr/local/XYZ 内にインストールされている場合は、次のシンボリックリンクを確立するために、インストール後実行スクリプトが必要です。


$ ln -s ../ABC /usr/local/XYZ/ABC

ユーザーの PATH/usr/local/XYZ/bin が追加される場合、アプリケーション xyz の呼び出しによって、次のようにパス名検索でその依存関係が探されます。


$ ldd -s xyz
.....
  find object=libX.so.1; required by xyz
    search path=$ORIGIN/../lib:$ORIGIN/../ABC/lib  (RUNPATH/RPATH from file xyz)
      trying path=/usr/local/XYZ/lib/libX.so.1
        libX.so.1 =>     /usr/local/XYZ/lib/libX.so.1
 
  find object=libA.so.1; required by xyz
    search path=$ORIGIN/../lib:$ORIGIN/../ABC/lib  (RUNPATH/RPATH from file xyz)
      trying path=/usr/local/XYZ/lib/libA.so.1
      trying path=/usr/local/ABC/lib/libA.so.1
        libA.so.1 =>     /usr/local/ABC/lib/libA.so.1
 
  find object=libY.so.1; required by /usr/local/XYZ/lib/libX.so.1
     search path=$ORIGIN:$ORIGIN/../ABC/lib \
                (RUNPATH/RPATH from file /usr/local/XYZ/lib/libX.so.1)
      trying path=/usr/local/XYZ/lib/libY.so.1
        libY.so.1 =>     /usr/local/XYZ/lib/libY.so.1
 
  find object=libC.so.1; required by /usr/local/XYZ/lib/libX.so.1
     search path=$ORIGIN:$ORIGIN/../ABC/lib \
                (RUNPATH/RPATH from file /usr/local/XYZ/lib/libX.so.1)
      trying path=/usr/local/XYZ/lib/libC.so.1
      trying path=/usr/local/ABC/lib/libC.so.1
        libC.so.1 =>     /usr/local/ABC/lib/libC.so.1
 
  find object=libB.so.1; required by /usr/local/ABC/lib/libA.so.1
     search path=$ORIGIN  (RUNPATH/RPATH from file /usr/local/ABC/lib/libA.so.1)
       trying path=/usr/local/ABC/lib/libB.so.1
        libB.so.1 =>     /usr/local/ABC/lib/libB.so.1

セキュリティー

セキュリティー保護されたプロセスでは、$ORIGIN 文字列の拡張は、それがトラストディレクトリに拡張されるときにかぎり許可されます。ほかの相対パス名は、セキュリティーリスクを伴います。

$ORIGIN/../lib のようなパスは一定の場所 (実行可能プログラムの場所で特定される) を指しているように見えますが、それは正しくありません。同じファイルシステム内の書き込み可能なディレクトリにより、$ORIGIN を使用するセキュリティー保護されたプログラムが不当に利用される可能性があります。

次の例は、$ORIGIN がセキュリティー保護されたプロセス内で任意に拡張された場合、セキュリティー侵入が生じる可能性があることを示しています。


$ cd /worldwritable/dir/in/same/fs
$ mkdir bin lib
$ ln $ORIGIN/bin/program bin/program
$ cp ~/crooked-libc.so.1 lib/libc.so.1
$ bin/program
..... using crooked-libc.so.1

ユーティリティー crle(1) を使用すれば、セキュリティー保護されたアプリケーションによる $ORIGIN の使用を可能にするトラストディレクトリを指定できます。この方法を使用する場合には、管理者は、ターゲットディレクトリを悪意のある侵入から適切に保護する必要があります。