JavaScript is required to for searching.
ナビゲーションリンクをスキップ
印刷ビューの終了
リンカーとライブラリ     Oracle Solaris 10 8/11 Information Library (日本語)
search filter icon
search icon

ドキュメントの情報

はじめに

1.  Oracle Solaris リンカーの紹介

2.  リンカー

3.  実行時リンカー

4.  共有オブジェクト

命名規約

共有オブジェクト名の記録

アーカイブへの共有オブジェクトの取り込み

記録名の衝突

依存関係を持つ共有オブジェクト

依存関係の順序

フィルタとしての共有オブジェクト

標準フィルタの生成

補助フィルタの生成

フィルタ処理の組み合わせ

「フィルティー」の処理

性能に関する考慮事項

elfdump を使用したファイルの解析

基本システム

動的依存関係の遅延読み込み

位置独立のコード

SPARC: -K pic-K PIC オプション

使用されない対象物の削除

共有可能性の最大化

テキストへの読み取り専用データの移動

多重定義されたデータの短縮

自動変数の使用

バッファーの動的割り当て

ページング回数の削減

再配置

シンボルの検索

再配置が実行されるとき

再配置セクションの結合

コピー再配置

-B symbolic オプションの使用

共有オブジェクトのプロファイリング

5.  アプリケーションバイナリインタフェースとバージョン管理

6.  サポートインタフェース

7.  オブジェクトファイル形式

8.  スレッド固有領域 (TLS)

9.  mapfile

A.  リンカーのクイックリファレンス

B.  バージョン管理の手引き

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

D.  直接結合

E.  System V Release 4 (バージョン 1) Mapfile

F.  リンカーとライブラリのアップデートおよび新機能

索引

性能に関する考慮事項

共有オブジェクトは、同じシステム内の複数のアプリケーションで使用できます。共有オブジェクトの性能は、共有オブジェクトを使用するアプリケーションだけでなく、システム全体に影響します。

共有オブジェクト内のコードは、実行中のプロセスの性能に直接影響しますが、ここでは共有オブジェクトの実行時処理に関連した性能の問題を説明します。次の節では、再配置によるオーバーヘッドとともに、テキストサイズや純度 (purity) などの面についても見ながら、この処理について詳しく説明します。

elfdump を使用したファイルの解析

ELF ファイルの内容を解析するために、標準の UNIX ユーティリティー dump(1)nm(1)size(1) など、さまざまなツールが使用できます。Oracle Solaris では、これらのツールは大部分が elfdump(1) に置き換えられました。

ELF 形式では、データがセクションにまとめられます。次に、セクションはセグメントと呼ばれる単位に割り当てられます。いくつかのセグメントには、ファイルの一部がどのようにメモリーにマッピングされるかが記述されます。mmap(2) のマニュアルページを参照してください。これらの読み込み可能セグメントは、elfdump(1) コマンドを使用して、LOAD エントリを調べることによって表示できます。

$ elfdump -p -NPT_LOAD libfoo.so.1
Program Header[0]:
    p_vaddr:      0           p_flags:    [ PF_X PF_R ]
    p_paddr:      0           p_type:     [ PT_LOAD ]
    p_filesz:     0x53c       p_memsz:    0x53c
    p_offset:     0           p_align:    0x10000

Program Header[1]:
    p_vaddr:      0x1053c     p_flags:    [ PF_X PF_W PF_R ]
    p_paddr:      0           p_type:     [ PT_LOAD ]
    p_filesz:     0x114       p_memsz:    0x13c
    p_offset:     0x53c       p_align:    0x10000
....

共有オブジェクト libfoo.so.1 には、一般に「テキスト」セグメントおよび「データ」セグメントと呼ばれる 2 つの読み込み可能なセグメントがあります。テキストセグメントがマップされ、その内容 PF_X PF_R の読み込みと実行が可能になります。データセグメントもマップされ、その内容 PF_W が変更できるようになります。データセグメントのメモリーサイズ (p_memsz) は、ファイルサイズ (p_filesz) とは異なります。この違いは、データセグメントの一部であり、セグメントが読み込まれると動的に作成される .bss セクションを示すものです。

プログラマがファイルについて考えるとき、多くの場合、そのコード内の関数とデータ要素を定義するシンボルの点から考えます。これらのシンボルは、elfdump-s オプションを使用すると表示できます。次に例を示します。

$ elfdump -sN.symtab libfoo.so.1

Symbol Table Section:  .symtab
     index    value      size      type bind oth ver shndx          name
      ....
      [36]  0x00010628 0x00000028  OBJT GLOB  D    0 .data          data
      ....
      [38]  0x00010650 0x00000028  OBJT GLOB  D    0 .bss           bss

      ....
      [40]  0x00000520 0x0000000c  FUNC GLOB  D    0 .init          _init
      ....
      [44]  0x00000508 0x00000014  FUNC GLOB  D    0 .text          foo
      ....
      [46]  0x0000052c 0x0000000c  FUNC GLOB  D    0 .fini          _fini

elfdump で表示されるシンボルテーブル情報には、シンボルに関連するセクションが含まれます。elfdump-c オプションを使用すると、これらのセクションに関する情報を表示できます。

$ elfdump -c libfoo.so.1
....
Section Header[6]:  sh_name: .text
    sh_addr:      0x4f8           sh_flags:   [ SHF_ALLOC SHF_EXECINSTR ]
    sh_size:      0x28            sh_type:    [ SHT_PROGBITS ]
    sh_offset:    0x4f8           sh_entsize: 0
    sh_link:      0               sh_info:    0
    sh_addralign: 0x8       

Section Header[7]:  sh_name: .init
    sh_addr:      0x520           sh_flags:   [ SHF_ALLOC SHF_EXECINSTR ]
    sh_size:      0xc             sh_type:    [ SHT_PROGBITS ]
    sh_offset:    0x520           sh_entsize: 0
    sh_link:      0               sh_info:    0
    sh_addralign: 0x4       

Section Header[8]:  sh_name: .fini
    sh_addr:      0x52c           sh_flags:   [ SHF_ALLOC SHF_EXECINSTR ]
    sh_size:      0xc             sh_type:    [ SHT_PROGBITS ]
    sh_offset:    0x52c           sh_entsize: 0
    sh_link:      0               sh_info:    0
    sh_addralign: 0x4       
....
Section Header[12]:  sh_name: .data
    sh_addr:      0x10628         sh_flags:   [ SHF_WRITE SHF_ALLOC ]
    sh_size:      0x28            sh_type:    [ SHT_PROGBITS ]
    sh_offset:    0x628           sh_entsize: 0
    sh_link:      0               sh_info:    0
    sh_addralign: 0x4       
....
Section Header[14]:  sh_name: .bss
    sh_addr:      0x10650         sh_flags:   [ SHF_WRITE SHF_ALLOC ]
    sh_size:      0x28            sh_type:    [ SHT_NOBITS ]
    sh_offset:    0x650           sh_entsize: 0
    sh_link:      0               sh_info:    0
    sh_addralign: 0x4       
....

上記の例の elfdump(1) からの出力は、関数 _initfoo、および _fini と、セクション .init.text、および .fini との関連を示します。これらのセクションは読み取り専用であるため、「テキスト」セグメントの一部です。

同様に、データ配列 databss は、それぞれセクション .data.bss に関連付けられています。これらのセクションは書き込み可能であるため、「データ」セグメントの一部です。

基本システム

アプリケーションがある共有オブジェクトを使用して構築される場合、そのオブジェクトの読み込み可能な内容全体が、実行時にそのプロセスの仮想アドレス空間に割り当てられます。共有オブジェクトを使用する各プロセスは、まずメモリー内にある共有オブジェクトの単一のコピーを参照します。

共有オブジェクト内の再配置は処理されて、シンボリック参照を該当する定義に結合します。これにより、共有オブジェクトがリンカーによって生成されたときには得られなかった真の仮想アドレスが計算されます。通常、これらの再配置によって、プロセスのデータセグメント内のエントリが更新されます。

メモリー管理スキーマは、プロセス間で共有オブジェクトの共有メモリーをページ細部のレベルで動的リンクするときの基本となります。メモリーページは、実行時に変更されていなければ共有できます。プロセスは、データ項目の書き込み時、または共有オブジェクトへの参照の再配置時に共有オブジェクトのページに書き込む場合、そのページの専用コピーを生成します。この専用コピーは、共有オブジェクトのほかのユーザーに対して何も影響しません。ただし、このページはほかのプロセス間での共有に伴う利点をすべて失います。この方法で変更されたテキストページは、「純粋でない」(impure) と呼ばれます。

メモリーにマッピングされた共有オブジェクトのセグメントは、参照専用の「テキスト」セグメントと読み書きが可能な「データ」セグメントに分類されます。ELF ファイルからこの情報を取得する方法については、elfdump を使用したファイルの解析」を参照してください。共有オブジェクトを開発するときの主要目的は、テキストセグメントを最大化して、データセグメントを最小化することにあります。これにより、共有オブジェクトの初期設定と使用に必要な処理の量を削減しながら、コード共有の量を最適化できます。次の節では、この目的を達成するために役立つメカニズムを示します。

動的依存関係の遅延読み込み

オブジェクトを遅延読み込みするように設定すると、共有オブジェクトの依存関係の読み込みは、最初に参照されるまで延期できます。「動的依存関係の遅延読み込み」を参照してください。

小さいアプリケーションの場合、典型的な 1 つの実行スレッドでアプリケーションのすべての依存関係を参照することができます。この場合、依存関係が遅延読み込み可に設定されているかどうかに関係なく、アプリケーションはすべての依存関係を読み込みます。しかし、遅延読み込みでは依存関係の処理が処理の起動時から延期され、処理の実行期間全体にわたって広がります。

多くの依存関係を持つアプリケーションの場合、遅延読み込みを使用すると、一部の依存関係がまったく読み込まれないことがあります。特定の実行スレッドで参照されない依存関係は読み込まれません。

位置独立のコード

動的実行可能ファイル内のコードは通常「位置に依存」し、メモリー内の固定アドレスに結び付けられています。一方、共有オブジェクトは、異なるプロセス内の異なる位置に読み込むことができます。位置独立のコードは、特定のアドレスに結び付けられていません。このように独立しているため、コードは、そのコードを使用する各プロセス内の異なるアドレスで実際に実行できます。位置独立のコードは、共有オブジェクトを作成する場合に推奨します。

コンパイラは、-K pic オプションによって、位置独立のコードを生成できます。

共有オブジェクトが位置に依存するコードで構築されている場合、テキストセグメントには実行時に変更が必要となる場合があります。このような変更により、再配置可能な参照を、オブジェクトが読み込まれている位置に割り当てることができます。テキストセグメントの再配置を行うには、セグメントを書き込み可能として再度マッピングする必要があります。このような変更にはスワップ空間の予約が必要で、またプロセスのテキストセグメントの非公開コピーが行われます。テキストセグメントは複数のプロセス間では共有できなくなります。位置に依存するコードは、通常、対応する位置独立のコードよりも多くの実行時再配置を必要とします。概して、テキスト再配置を処理するオーバーヘッドは、重大な性能の低下の原因になる可能性があります。

位置独立のコードから構築された共有オブジェクトでは、そのデータセグメント内のデータを介した間接参照として、再配置可能な参照が生成されます。テキストセグメント内のコードは変更する必要はありません。すべての再配置更新がデータセグメント内の対応するエントリに適用されます。特定の間接参照のテクニックの詳細については、「大域オフセットテーブル (プロセッサ固有)」「プロシージャーのリンクテーブル (プロセッサ固有)」を参照してください。

このような再配置が存在する場合、実行時リンカーはテキスト再配置を処理しようとします。ただし、一部の再配置は実行時に処理できません。

x64 の位置に依存するコードのシーケンスは、下位 32 ビットのメモリーにのみ読み込み可能なコードを生成します。上位 32 ビットのアドレスはすべてゼロである必要があります。通常、共有オブジェクトはメモリーの最上位に読み込まれるため、上位 32 ビットのアドレスが必要になります。そのため、x64 共有オブジェクト内の、位置に依存するコードは、再配置の要件に対処するのに不十分です。共有オブジェクト内でそのようなコードを使用すると、実行時再配置エラーが発生する可能性があります。

$ prog
ld.so.1: prog: fatal: relocation error: R_AMD64_32: file \
    libfoo.so.1: symbol (unknown): value 0xfffffd7fff0cd457 does not fit

位置独立のコードはメモリー内の任意の場所に読み込めるため、x64 の共有オブジェクトの要件を満たします。

このような状況は、64 ビット SPARCV9 コードに使用されるデフォルトの ABS64 モードとは異なります。位置に依存するこのコードは通常、完全な 64 ビットアドレス範囲と互換性があります。したがって、位置に依存するコードのシーケンスは、SPARCV9 共有オブジェクト内に存在できます。64 ビット SPARCV9 コードに ABS32 モードまたは ABS44 モードのいずれかを使用しても、実行時に解決できない再配置が生じる可能性があります。ただし、これらの各モードでは、実行時リンカーがテキストセグメントを再配置する必要があります。

実行時リンカーの機能や、再配置要件の違いに関係なく、共有オブジェクトは位置独立のコードを使用して構築するべきです。

共有オブジェクトのうち、テキストセグメントに対して再配置を必要とするものを識別できます。次の例では、elfdump(1) を使用して、TEXTREL エントリという動的エントリが存在するかどうかを判別します。

$ cc -o libfoo.so.1 -G -R. foo.c
$ elfdump -d libfoo.so.1 | grep TEXTREL
       [9]  TEXTREL       0

注 - TEXTREL エントリの値は関係ありません。共有オブジェクトにこのエントリが存在する場合は、テキスト再配置があることを示しています。


テキスト再配置を含む共有オブジェクトが作成されるのを防ぐには、リンカーの -z text フラグを使用します。このフラグを使用すると、リンカーは、入力として使用された位置に依存するすべてのコードの出所を指摘する診断を生成します。次の例に、位置に依存するコードが、共有オブジェクトの生成にどのように失敗するかを示します。

$ cc -o libfoo.so.1 -z text -G -R. foo.c
Text relocation remains                       referenced
    against symbol                  offset      in file
foo                                 0x0         foo.o
bar                                 0x8         foo.o
ld: fatal: relocations remain against allocatable but \
non-writable sections

ファイル foo.o から位置依存のコードが生成されたために、テキストセグメントに対して 2 つの再配置が生成されています。これらの診断は、可能な場合、再配置の実行に必要なシンボリック参照すべてを示します。この場合、再配置はシンボル foobar に対するものです。

手書きのアセンブラコードが含まれ、その中に、位置独立の適切なプロトタイプが含まれていない場合、共有オブジェクト内ではテキスト再配置が発生する可能性もあります。


注 - いくつかの単純なソースファイルをテストしながら、位置に依存しないコードを決定することもできます。中間アセンブラ出力を生成するコンパイラ機能を使用してください。


SPARC: -K pic-K PIC オプション

SPARC バイナリでは、-K pic オプションと代替の -K PIC オプションの動作がわずかに違っており、大域オフセットテーブルエントリの参照方法が異なります。「大域オフセットテーブル (プロセッサ固有)」を参照してください。

大域オフセットテーブルはポインタの配列で、エントリのサイズは、32 ビット (4 バイト) および 64 ビット (8 バイト) に固定です。次のコード例は、-K pic を使用して生成されるエントリを参照します。

        ld    [%l7 + j], %o0    ! load &j into %o0

%l7 には、あらかじめ計算された参照元オブジェクトのシンボル _GLOBAL_OFFSET_TABLE_ の値が代入されます。

このコード例は、大域オフセットテーブルのエントリ用に 13 ビットの変位定数を提供します。つまり、この変位は、32 ビットのオブジェクトの場合は 2048 個の一意のエントリを提供し、64 ビットのオブジェクトの場合は 1024 個の一意のエントリを提供します。返されるエントリ数より多くのエントリを要求するオブジェクトの場合、リンカーは致命的なエラーを生成します。

$ cc -K pic -G -o lobfoo.so.1 a.o b.o ... z.o
ld: fatal: too many symbols require `small' PIC references:
        have 2050, maximum 2048 -- recompile some modules -K PIC.

このエラー状態を解決するには、入力再配置可能オブジェクトの一部をコンパイルするときに、-K PIC オプションを指定します。このオプションは、32 ビットの定数を大域オフセットテーブルエントリに使用します。

        sethi %hi(j), %g1
        or    %g1, %lo(j), %g1    ! get 32–bit constant GOT offset
        ld    [%l7 + %g1], %o0    ! load &j into %o0

elfdump(1)-G オプションとともに使用すれば、オブジェクトの大域オフセットテーブルの要件を調べることができます。リンカーのデバッグトークン -D got,detail を使用すれば、リンク編集中のこれらのエントリの処理を確認することもできます。

頻繁にアクセスするデータ項目に対しては、-K pic を使用する方法が有利です。どちらの方法でもエントリを参照することはできます。しかし、再配置可能オブジェクトをどちらの方法でコンパイルしたらいいのか決めるのには時間がかかる上、性能はわずかしか改善されません。すべての再配置型オブジェクトを -K PIC オプションを指定して再コンパイルする方が一般には簡単です。

使用されない対象物の削除

構築中のオブジェクトによって使用されない関数やデータを保持することは無駄です。この対象物によってオブジェクトが肥大し、不必要な再配置のオーバーヘッドやそれに関連したページング動作が生じます。使用されない依存関係への参照も無駄です。これらの参照によって、ほかの共有オブジェクトの不必要な読み込みと処理が生じます。

リンカーのデバッギングトークン -D unused を使用すると、リンク編集中に使用されないセクションが表示されます。未使用として特定されるセクションは、リンク編集から削除してください。未使用セクションは、リンカーの -z ignore オプションを使用して削除できます。

リンカーは、次の条件のときに再配置可能オブジェクトのセクションを未使用であると判断します。

共有オブジェクトの外部インタフェースを定義することによって、セクションを削除するリンカーの機能を改善することができます。インタフェースを定義することによって、インタフェースの一部として定義されなかった大域シンボルは局所シンボルになります。ほかのオブジェクトから参照されていない局所シンボルは、排除の候補であると明確に識別されます。

関数やデータ変数が独自のセクションに割り当てられている場合、リンカーはこのような関数やデータ変数を個別に排除できます。このセクションの細分化は、-xF などのコンパイラオプションを使用して行います。以前のコンパイラには、関数を独自のセクションに割り当てる機能しかありませんでした。最近のコンパイラでは、-xF 構文が拡張されて、データ変数を独自のセクションに割り当てることができます。以前のコンパイラでは、-xF を使用するときには、C++ 例外処理を無効にする必要がありました。最近のコンパイラでは、この制限はなくなりました。

再配置可能オブジェクトの割り当て可能なセクションすべてが排除可能な場合、そのファイル全体がリンク編集から削除されます。

入力ファイルの排除に加えて、リンカーは使用されていない依存関係を判断できます。構築しているオブジェクトによって結合されていない場合、その依存関係は使用されていないと判断されます。未使用の依存関係の記録を排除するために、-z ignore オプションでオブジェクトを使用できます。

-z ignore オプションが適用されるのは、リンカーのコマンド行上でこのオプションのあとに指定したファイルだけです。-z ignore オプションは -z record によって取り消されます。

共有可能性の最大化

「基本システム」で説明したように、共有オブジェクトのテキストセグメントだけが、それを使用するすべてのプロセスによって共有されます。オブジェクトのデータセグメントは、通常共有されません。共有オブジェクトを使用する各プロセスは、そのデータセグメント全体の専用メモリーコピーをそのセグメント内に書き込まれるデータ項目として生成します。データセグメントを削減するには、テキストセグメントに書き込まれることがないデータ要素を移動するか、またはデータ項目を完全に削除します。

次の節では、データセグメントのサイズを削減するために使用できるいくつかのメカニズムについて説明します。

テキストへの読み取り専用データの移動

読み取り専用のデータ要素はすべて、const 宣言を使用して、テキストセグメントに移動する必要があります。たとえば、次の文字列は、書き込み可能なデータセグメントの一部である .data セクションにあります。

char *rdstr = "this is a read-only string";

これに対して、次の文字列は、テキストセグメント内にある読み取り専用データセクションである .rodata セクション内にあります。

const char *rdstr = "this is a read-only string";

読み取り専用要素をテキストセグメントに移動することによるデータセグメントの削減は目的に沿うものです。ただし、再配置を必要とするデータ要素を移動すると、逆効果になるおそれがあります。たとえば、次の文字列配列があるとします。

char *rdstrs[] = { "this is a read-only string",
                    "this is another read-only string" };

次の定義を使用するほうが良いと思われるかもしれません。

const char *const rdstrs[] = { ..... };

この定義により、文字列とこれらの文字列へのポインタ配列は、確実に .rodata セクションに置かれます。ただし、ユーザーがアドレス配列を読み取り専用と認識しても、実行時にはこれらのアドレスを再配置しなければなりません。したがって、この定義では再配置が作成されます。配列を次のように表現してみます。

const char *rdstrs[] = { ..... };

配列ポインタは、再配置できる書き込み可能なデータセグメント内に保持されます。配列文字列は、読み取り専用のテキストセグメント内に保持されます。


注 - コンパイラによっては、位置独立のコードを生成するときに、実行時に再配置を行うことになる読み取り専用割り当てを検出できるものがあります。このようなコンパイラは、このような項目を書き込み可能なセグメントに配置します。たとえば、.picdata です。


多重定義されたデータの短縮

多重定義されたデータを短縮すると、データを削減できます。同じエラーメッセージが複数回発生するプログラムの場合は、1 つの大域なデータを定義し、ほかのインスタンスすべてにこれを参照させると効率が良くなります。次に例を示します。

const char *Errmsg = "prog: error encountered: %d";

foo()
{
        ......
        (void) fprintf(stderr, Errmsg, error);
        ......

この種のデータ削減に適した対象は文字列です。共有オブジェクトでの文字列の使用は、strings(1) を使用して調べることができます。次の例では、ファイル libfoo.so.1 内に、データ文字列のソートされたリストを生成します。このリスト内の各項目には、文字列の出現回数を示す接頭辞が付いています。

$ strings -10 libfoo.so.1 | sort | uniq -c | sort -rn

自動変数の使用

データ項目用の常時記憶領域は、関連する機能が自動 (スタック) 変数を使用するように設計できる場合、完全に削除することができます。常時記憶領域を少しでも削除すると、通常これに対応して、必要な実行時再配置の数も減ります。

バッファーの動的割り当て

大きなデータバッファーは、通常、常時記憶領域を使用して定義するのではなく、動的に割り当てる必要があります。これにより、アプリケーションの現在の呼び出しで必要なバッファーだけが割り当てられるため、メモリー全体を節約できます。動的割り当てを行うと、互換性に影響を与えることなくバッファーのサイズを変更できるため、柔軟性も増します。

ページング回数の削減

新しいページにアクセスするすべてのプロセスでページフォルトが発生します。これはコストのかかる操作です。共有オブジェクトは多数のプロセスで使用できるため、共有オブジェクトへのアクセスによって生成されるページフォルトの数を減らすと、プロセスおよびシステム全体の効率が改善される可能性があります。

使用頻度の高いルーチンとそのデータを隣接するページの集合として編成すると、参照の効率が良くなるため、性能は通常向上します。あるプロセスがこれらの関数の 1 つを呼び出すとき、この関数がすでにメモリー内にある場合があります。これは、この関数が、使用頻度の高いほかの関数のすぐ近くに存在するためです。同様に、相互に関連する関数をグループ化すると、参照効率が向上します。たとえば、関数 foo() への呼び出しによって、常に関数 bar() が呼び出される場合は、これらの関数を同じページ上に置きます。cflow(1)、tcov(1)、prof(1)、および gprof(1) などのツールは、コードカバレージとプロファイリングを判定するために役立ちます。

関連する機能は、各自の共有オブジェクトに分離してください。標準 C ライブラリは従来、関連しない多数の関数を含んで構築されていました。たとえば、単一の実行可能ファイルがこのライブラリ内のすべてを使用することはほとんどありません。このライブラリは広範囲に使用されるため、実際に使用頻度のもっとも高い関数がどれかを判定することもかなり困難です。これに対して、共有オブジェクトを最初から設計する場合は、関連する関数だけを共有オブジェクト内に保持してください。これにより、参照の近傍性が改善するだけでなく、オブジェクト全体のサイズを減らすという効果も得られます。

再配置

「再配置処理」では、実行時リンカーが動的実行可能ファイルと共有オブジェクトを再配置して、「実行可能」プロセスを作成するためのメカニズムについて説明しました。「再配置シンボルの検索」「再配置が実行されるとき」は、この再配置処理を 2 つの領域に分類して、関連のメカニズムを簡素化して説明しています。これらの 2 つのカテゴリは、再配置による性能への影響を考慮するためにも最適です。

シンボルの検索

実行時リンカーは、特定のシンボルを検索する必要が生じると、デフォルトでは各オブジェクト内でそのシンボルを検索します。まず動的実行可能プログラムから検索してから、 オブジェクトが読み込まれた順番に共有オブジェクトを検索します。ほとんどの場合、シンボル再配置を必要とする共有オブジェクトは、シンボル定義の提供者になります。

この状況では、この再配置に使用されるシンボルが共有オブジェクトのインタフェースの一部として必要ではない場合、このシンボルは「静的」変数または「自動」変数に変換される可能性が高くなります。シンボル削減は、共有オブジェクトのインタフェースから削除されたシンボルにも適用できます。詳細は、「シンボル範囲の縮小」を参照してください。これらの変換を行うことによって、リンカーは、共有オブジェクトの作成中にこれらのシンボルに対するシンボル再配置を処理しなければならなくなります。

共有オブジェクトから表示できなければならない唯一の大域データ項目は、そのユーザーインタフェースに関するものです。しかし、大域データは異なる複数のソースファイルにある複数の関数から参照できるように定義されていることが多いため、これは歴史的に達成が困難です。シンボルの縮小を適用することによって、不要な大域シンボルを削除できます。「シンボル範囲の縮小」を参照してください。共有オブジェクトからエクスポートされた大域シンボルの数を少しでも減らせば、再配置のコストを削減し、性能全体を向上させることができます。

直接結合を使用すると、多数のシンボル再配置や依存関係を伴う動的プロセスでのシンボル検索オーバーヘッドも大幅に削減できます。付録 D 直接結合を参照してください。

再配置が実行されるとき

すべての即時参照再配置は、アプリケーションが制御を取得する前の、プロセスの初期設定中に実行する必要があります。これに対して、遅延参照は、関数の最初のインスタンスが呼び出されるまで延期できます。即時参照は通常、データ参照によって行われます。このため、データ参照の数を少なくすることによって、プロセスの実行時初期設定も削減されます。

初期設定再配置コストは、データ参照を関数参照に変換して延期することもできます。たとえば、機能インタフェースによってデータ項目を返すことができます。この変換を行うと、初期設定再配置コストがプロセスの実行期間中に効率的に分配されるため、性能は明らかに向上します。いくつかの機能インタフェースはプロセスの特定の呼び出しでは決して呼び出されない可能性もあるため、それらの再配置オーバーヘッドもすべてなくなります。

機能インタフェースを使用した場合の利点については、「コピー再配置」で説明します。この節では、動的実行可能ファイルと共有オブジェクトの間で使用される特殊でコストのかかる再配置メカニズムについて説明します。また、この再配置によるオーバーヘッドを回避する方法の例も示します。

再配置セクションの結合

再配置可能オブジェクト内の再配置セクションは通常、再配置の適用対象となるセクションとの 1 対 1 の関係が維持されます。ただし、リンカーエディタによって実行可能ファイルまたは共有オブジェクトが作成するときに、プロシージャーリンクテーブルの再配置を除くすべての再配置が .SUNW_reloc という名前の 1 つの共通セクションに配置されます。

この方法で再配置レコードを結合すると、すべての RELATIVE 再配置を 1 つにグループ化できます。すべてのシンボルの再配置は、シンボル名によって並べ替えられます。RELATIVE 再配置をグループ化すると、DT_RELACOUNT/DT_RELCOUNT .dynamic エントリを使用した最適な実行時処理が行われます。シンボルのエントリを並べ替えると、実行時にシンボルを検索する時間を削減できます

コピー再配置

共有オブジェクトは、通常、位置独立のコードによって構築されます。このタイプのコードから外部データ項目への参照は、1 組のテーブルによる間接アドレス指定を使用します。詳細は、「位置独立のコード」を参照してください。これらのテーブルは、データ項目の実アドレスによって実行時に更新されます。これらの更新されたテーブルによって、コード自体を変更することなくデータにアクセスすることができます。

ただし、動的実行可能ファイルは通常、位置独立のコードからは作成されません。これらのファイルが作成する外部データへの参照は、その参照を行うコードを変更することによって実行時にしか実行できないように見えます。読み取り専用のテキストセグメントの変更は、回避する必要があります。コピー再配置という再配置手法が、この参照を解決するために使用されます。

動的実行可能ファイルを作成するためにリンカーが使用され、データ項目への参照が依存共有オブジェクトのどれかに常駐するとします。動的実行可能ファイルの .bss で、共有オブジェクト内のデータ項目のサイズに等しいスペースが割り当てられます。このスペースには、共有オブジェクトに定義されているのと同じシンボリック名も割り当てられます。リンカーは、このデータ割り当てとともに特殊なコピー再配置レコードを生成して、実行時リンカーに対し、共有オブジェクトから動的実行可能ファイル内のこの割り当てスペースへデータをコピーするように指示します。

このスペースに割り当てられたシンボルは大域であるため、すべての共有オブジェクトからのすべての参照を満たすために使用されます。動的実行可能ファイルは、データ項目を継承します。この項目を参照するプロセス内のほかのオブジェクトすべてが、このコピーに結合されます。コピーの元となるデータは使用されなくなります。

このメカニズムの次の例では、標準 C ライブラリ内で保持されるシステムエラーメッセージの配列を使用します。SunOS オペレーティングシステムの以前のリリースでは、この情報へのインタフェースが、2 つの大域変数 sys_errlist[] および sys_nerr によって提供されました。最初の変数はエラーメッセージ文字列を提供し、2 つめの変数は配列自体のサイズを示しました。これらの変数はアプリケーション内で、通常次のように使用されていました。

$ cat foo.c
extern int  sys_nerr;
extern char *sys_errlist[];

char *
error(int errnumb)
{
        if ((errnumb < 0) || (errnumb >= sys_nerr))
                return (0);
        return (sys_errlist[errnumb]);
}

アプリケーションは、関数 error を使用して、番号 errnumb に対応するシステムエラーメッセージを取得します。

このコードを使用して作成された動的実行可能ファイルを調べると、コピー再配置の実装が更に詳細に示されます。

$ cc -o prog main.c foo.c
$ elfdump -sN.dynsym prog | grep ' sys_'
      [24]  0x00021240 0x00000260  OBJT GLOB  D    1 .bss           sys_errlist
      [39]  0x00021230 0x00000004  OBJT GLOB  D    1 .bss           sys_nerr
$ elfdump -c prog
....
Section Header[19]:  sh_name: .bss
    sh_addr:      0x21230         sh_flags:   [ SHF_WRITE SHF_ALLOC ]
    sh_size:      0x270           sh_type:    [ SHT_NOBITS ]
    sh_offset:    0x1230          sh_entsize: 0
    sh_link:      0               sh_info:    0
    sh_addralign: 0x8
....
$ elfdump -r prog

Relocation Section:  .SUNW_reloc
    type                       offset     addend  section        symbol
....
  R_SPARC_COPY                0x21240          0  .SUNW_reloc    sys_errlist
  R_SPARC_COPY                0x21230          0  .SUNW_reloc    sys_nerr
....

リンカーは、動的実行可能ファイルの .bss にスペースを割り当てて、sys_errlist および sys_nerr によって表されるデータを受け取っています。これらのデータは、プロセス初期設定時に、実行時リンカーによって C ライブラリからコピーされます。このため、これらのデータを使用する各アプリケーションは、データの専用コピーを各自のデータセグメントで取得します。

この手法には、実際には 2 つの欠点があります。まず、各アプリケーションでは、実行時のデータコピーによるオーバーヘッドによって性能が低下します。もう 1 つは、データ配列 sys_errlist のサイズが、C ライブラリのインタフェースの一部になるという点です。新しいエラーメッセージが追加されるなど、この配列のサイズが変わったとします。この配列を参照する動的実行可能ファイルすべてで、新しいエラーメッセージにアクセスするための新しいリンク編集を行う必要があります。この新しいリンク編集が行われないと、動的実行可能ファイル内の割り当てスペースが不足して、新しいデータを保持できません。

このような欠点は、動的実行可能ファイルに必要なデータが機能インタフェースによって提供されればなくなります。ANSI C 関数 strerror(3C) は、提示されたエラー番号に基づいて該当するエラー文字列へのポインタを返します。この関数の実装状態は次のようになります。

$ cat strerror.c
static const char *sys_errlist[] = {
        "Error 0",
        "Not owner",
        "No such file or directory",
        ......
};
static const int sys_nerr =
        sizeof (sys_errlist) / sizeof (char *);

char *
strerror(int errnum)
{
        if ((errnum < 0) || (errnum >= sys_nerr))
                return (0);
        return ((char *)sys_errlist[errnum]);
}

foo.c のエラールーチンは、ここではこの機能インタフェースを使用するように単純化できます。これによって、プロセス初期設定時に元のコピー再配置を実行する必要がなくなります。

また、データは共有オブジェクト限定のものであるため、そのインタフェースの一部ではなくなります。したがって、共有オブジェクトは、データを使用する動的実行可能ファイルに悪影響を与えることなく、自由にデータを変更できます。共有オブジェクトのインタフェースからデータ項目を削除すると、一般に共有オブジェクトのインタフェースとコードが維持しやすくなるとともに、性能も向上します。

ldd(1)-d オプションまたは -r オプションのどちらかをつけて使用すると、動的実行可能ファイル内にコピー再配置があるかどうかを検査できます。

たとえば、動的実行可能ファイル prog が当初、次の 2 つのコピー再配置が記録されるように、共有オブジェクト libfoo.so.1 に対して構築されている場合を考えます。

$ cat foo.c
int _size_gets_smaller[16];
int _size_gets_larger[16];
$ cc -o libfoo.so -G foo.c
$ cc -o prog main.c -L. -R. -lfoo
$ elfdump -sN.symtab prog | grep _size
      [49]  0x000211d0 0x00000040  OBJT GLOB  D    0 .bss           _size_gets_larger
      [59]  0x00021190 0x00000040  OBJT GLOB  D    0 .bss           _size_gets_smaller
$ elfdump -r prog | grep _size
  R_SPARC_COPY                0x211d0          0  .SUNW_reloc    _size_gets_larger
  R_SPARC_COPY                0x21190          0  .SUNW_reloc    _size_gets_smaller

これらのシンボルについて異なるサイズを含む、この共有オブジェクトの新しいバージョンが提供されているとします。

$ cat foo2.c
int _size_gets_smaller[4];
int _size_gets_larger[32];
$ cc -o libfoo.so -G foo2.c
$ elfdump -sN.symtab libfoo.so | grep _size
      [37]  0x000105cc 0x00000010  OBJT GLOB  D    0 .bss           _size_gets_smaller
      [41]  0x000105dc 0x00000080  OBJT GLOB  D    0 .bss           _size_gets_larger

この動的実行可能ファイルに対して ldd(1) を実行すると、次の結果が返されます。

$ ldd -d prog
    libfoo.so.1 =>   ./libfoo.so.1
    ....
      relocation R_SPARC_COPY sizes differ: _size_gets_larger
          (file prog size=0x40; file ./libfoo.so size=0x80)
          prog size used; possible data truncation
      relocation R_SPARC_COPY sizes differ: _size_gets_smaller
          (file prog size=0x40; file ./libfoo.so size=0x10)
          ./libfoo.so size used; possible insufficient data copied
....

ldd(1) は、動的実行可能ファイルが、共有オブジェクトが提供することができるデータすべてをコピーする一方で、その割り当てスペースで許容できる量しか受け付けないということを知らせています。

位置独立のコードだけでアプリケーションを作成すれば、コピー再配置を完全に排除することができます。「位置独立のコード」を参照してください。

-B symbolic オプションの使用

リンカーの -B symbolic オプションを使用すると、シンボルの参照を共有オブジェクト内の大域定義に結合できます。このオプションは、実行時リンカーそのものを作成するために設計されたという意味で、長い歴史があるといえます。

-B symbolic オプションを使用するときは、オブジェクトのインタフェースを定義し、非公開シンボルをローカルに縮小する必要があります。「シンボル範囲の縮小」を参照してください。-B symbolic を使用すると直感的にはわからない副産物ができることがあります。

シンボリックに結合されたシンボルが割り込まれた場合、シンボリックに結合されたオブジェクトの外からのそのシンボルへの参照は、その割り込みに結合します。オブジェクトそのものはすでに内部的に結合されています。本質的に、同じ名前を持つ 2 つのシンボルは、プロセス内から参照されます。シンボリックに結合されたデータシンボルは、コピーを再配置し、同じ割り込み状態を作成します。「コピー再配置」を参照してください。


注 - シンボリックに結合された共有オブジェクトは、.dynamic フラグ DF_SYMBOLIC で表されます。このタグは情報を提供するだけです。実行時リンカーは、これらのオブジェクトからのシンボルの検索をほかのオブジェクトからの場合と同じ方法で処理します。シンボリック結合はリンカーフェーズで作成されたものと想定されます。


共有オブジェクトのプロファイリング

実行時リンカーは、アプリケーションの実行中に処理された共有オブジェクトすべてのプロファイリング情報を生成できます。実行時リンカーは、共有オブジェクトをアプリケーションに結合しなくてはならないため、すべての大域関数結合を横取りすることができます。これらの結合は、.plt エントリによって起こります。このメカニズムの詳細は、「再配置が実行されるとき」を参照してください。

LD_PROFILE 環境変数には、プロファイル対象となる共有オブジェクトの名前を指定します。この環境変数を使用すると、単一の共有オブジェクトを解析できます。環境変数の設定は、1 つまたは複数のアプリケーションによる共有オブジェクトの使用を解析するために使用できます。次の例では、コマンド ls(1) の 1 回の呼び出しによる libc の使用が解析されます。

$ LD_PROFILE=libc.so.1 ls -l

次の例では、環境変数の設定は構成ファイルに記録されます。この設定によって、アプリケーションが libc を使用するたびに、解析情報が蓄積されます。

# crle -e LD_PROFILE=libc.so.1
$ ls -l
$ make
$ ...

プロファイル処理が有効化されると、プロファイルデータファイルがまだ存在していない場合にはそれが作成されます。このファイルは、実行時リンカーに割り当てられます。上記の例で、このデータファイルは /var/tmp/libc.so.1.profile です。64 ビットライブラリは、拡張プロファイル形式を必要とし、.profilex 接尾辞を使用して書かれます。代替ディレクトリを指定して、環境変数 LD_PROFILE_OUTPUT によってプロファイルデータを格納することもできます。

このプロファイルデータファイルは、profil(2) データを保存して、指定の共有オブジェクトの使用に関連するカウント情報を呼び出すために使用されます。このプロファイルデータは、gprof(1) によって直接調べることができます。


注 - gprof(1) は通常、cc(1) の -xpg オプションを使用してコンパイルされた実行可能ファイルにより作成された、gmon.out プロファイルデータを解析するために使用されます。実行時リンカーのプロファイル解析では、このオプションによってコードをコンパイルする必要はありません。依存共有オブジェクトがプロファイルされるアプリケーションは、profil(2) に対して呼び出しを行うことができません。これは、このシステム呼び出しでは、同じプロセス内で複数の呼び出しが行われないためです。同じ理由から、cc(1) の-xpg オプションによって、これらのアプリケーションをコンパイルすることもできません。このコンパイラによって生成されたプロファイリングのメカニズムが profil(2) の上にも構築されます。


このプロファイリングメカニズムのもっとも強力な機能の 1 つに、複数のアプリケーションに使用される共有オブジェクトの解析があります。通常、プロファイリング解析は、1 つまたは 2 つのアプリケーションを使用して実行されます。しかし共有オブジェクトは、その性質上、多数のアプリケーションで使用できます。これらのアプリケーションによる共有オブジェクトの使用方法を解析すると、共有オブジェクトの全体の性能を向上させるには、どこに注意すべきかを理解できます。

次の例は、ソース階層内でいくつかのアプリケーションを作成したときの libc の性能解析を示しています。

$ LD_PROFILE=libc.so.1 ; export LD_PROFILE
$ make
$ gprof -b /lib/libc.so.1 /var/tmp/libc.so.1.profile
.....

granularity: each sample hit covers 4 byte(s) ....

                                  called/total     parents
index  %time    self descendents  called+self    name      index
                                  called/total     children
.....
-----------------------------------------------
                0.33        0.00      52/29381     _gettxt [96]
                1.12        0.00     174/29381     _tzload [54]
               10.50        0.00    1634/29381     <external>
               16.14        0.00    2512/29381     _opendir [15]
              160.65        0.00   25009/29381     _endopen [3]
[2]     35.0  188.74        0.00   29381         _open [2]
-----------------------------------------------
.....
granularity: each sample hit covers 4 byte(s) ....

   %  cumulative    self              self    total         
 time   seconds   seconds    calls  ms/call  ms/call name   
 35.0     188.74   188.74    29381     6.42     6.42  _open [2]
 13.0     258.80    70.06    12094     5.79     5.79  _write [4]
  9.9     312.32    53.52    34303     1.56     1.56  _read [6]
  7.1     350.53    38.21     1177    32.46    32.46  _fork [9]
 ....

特殊名 <external> は、プロファイル中の共有オブジェクトのアドレス範囲外からの参照を示しています。したがって、上記の例では、1634 は、動的実行可能ファイルから、またはプロファイル解析の進行中に libc によって結合されたほかの共有オブジェクトから発生した libc 内の関数 open(2) を呼び出しています。


注 - 共有オブジェクトのプロファイルは、マルチスレッド化に対し安全です。ただし、あるスレッドがプロファイルデータ情報を更新しているときに、もう 1 つのスレッドが fork(2) を呼び出す場合は例外です。fork(2) を使用すると、この制限はなくなります。