リンカーとライブラリ

位置独立のコード

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

コンパイラは、-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 オプションを指定して再コンパイルする方が一般には簡単です。