リンカーとライブラリ

プロシージャのリンクテーブル (プロセッサ固有)

大域オフセットテーブルは位置に依存しないアドレスの計算を絶対位置に変換します。同様に、プロシージャのリンクテーブルは位置に依存しない関数呼び出しを絶対位置に変換します。リンカーは、ある 1 つの実行可能オブジェクトまたは共有オブジェクトから別の実行可能オブジェクトまたは共有オブジェクトへの実行転送 (関数呼び出しなど) を解決できません。このため、リンカーはプログラム転送制御をプロシージャのリンクテーブルのエントリに与えます。このようにして実行時リンカーは、位置からの独立性とプログラムのテキストの共有性を低下させることなくエントリをリダイレクトします。実行可能ファイルと共有オブジェクトファイルには、別個のプロシージャのリンクテーブルが存在します。

SPARC: 32 ビットプロシージャのリンクテーブル

32 ビット SPARC 動的オブジェクトの場合、プロシージャのリンクテーブルは専用データ内に存在します。実行時リンカーは、宛先の絶対アドレスを判定し、これらの絶対アドレスに従ってプロシージャのリンクテーブルのメモリーイメージに変更を加えます。

最初の 4 つのプロシージャのリンクテーブルエントリは、予約されています。表 7–48 に例示されてはいますが、これらのエントリの元の内容は指定されていません。テーブル内の各エントリは 3 ワード (12 バイト) を占めており、最後のテーブルエントリの後には nop 命令が続きます。

再配置テーブルは、プロシージャのリンクテーブルに関連付けられています。_DYNAMIC 配列の DT_JMP_REL エントリは、最初の再配置エントリの位置を与えます。再配置テーブルには、予約されていないプロシージャのリンクテーブルエントリごとに 1 つのエントリが同じ順番で存在します。各エントリの再配置タイプは、R_SPARC_JMP_SLOT です。再配置オフセットは関連付けられているプロシージャのリンクテーブルエントリの先頭バイトのアドレスを指定します。シンボルテーブルインデックスは適切なシンボルを参照します。

プロシージャのリンクテーブル機能を説明するため、表 7–48 に 4 つのエントリが示されています。最初の 2 つのエントリは予約されている最初の 4 つのエントリの内の 2 つであり、3 番目のエントリは name101 に対する呼び出しであり、4 番目のエントリは name102 に対する呼び出しです。この例では、name102 のエントリがテーブルの最後のエントリであることを前提としており、後に続く nop 命令が示されています。左欄は、動的リンクが行われる前のオブジェクトファイルの命令を示しています。右欄は、実行時リンカーがプロシージャのリンクテーブルエントリを変更した結果を示しています。

表 7–48 SPARC: プロシージャのリンクテーブルの例
オブジェクトファイルメモリーセグメント
.PLT0:
    unimp
    unimp
    unimp
.PLT1:
    unimp
    unimp
    unimp
.PLT0:
    save    %sp, -64, %sp
    call    runtime_linker
    nop
.PLT1:
    .word   identification
    unimp
    unimp
.PLT101:
    sethi   (.-.PLT0), %g1
    ba,a    .PLT0
    nop
.PLT102:
    sethi   (.-.PLT0), %g1
    ba,a    .PLT0
    nop

    nop
.PLT101:
    nop
    ba,a    name101
    nop
.PLT102:
    sethi   (.-.PLT0), %g1
    sethi   %hi(name102), %g1
    jmpl    %g1+%lo(name102), %g0
    
    nop

実行時リンカーとプログラムは、以下の手順に従ってプロシージャのリンクテーブル内のシンボル参照を協調して解決します。ただし、以下に記述されている手順は、単に説明用のためのものです。実行時リンカーの正確な実行時動作については、記述されていません。

  1. 実行時リンカーは、プログラムのメモリーイメージを最初に作成するとき、プロシージャのリンクテーブルの初期エントリに、実行時リンカー自身のルーチンの 1 つに制御を渡すように変更を加える。実行時リンカーはまた、識別情報 (identification) を 2 番目のエントリに格納する。実行時リンカーは、制御を受け取る際、このワードを調べることで、このルーチンを呼び出したオブジェクトを見つけることができる

  2. 他のすべてのプロシージャのリンクテーブルエントリは、最初は先頭エントリに渡される。これで、実行時リンカーは各テーブルエントリの最初の実行時に制御を取得する。たとえば、プログラムが name101 を呼び出すと、制御がラベル .PLT101 に渡される

  3. sethi 命令は、現在のプロシージャのリンクテーブルエントリ (.PLT101) と最初のプロシージャのリンクテーブルエントリ (.PLT0) の距離を計算する。この値は、%g1 レジスタの最上位 22 ビットを占める。

  4. 次に、ba,a 命令が .PLT0 にジャンプして、スタックフレームを作成し、実行時リンカーを呼び出す

  5. 実行時リンカーは、識別情報の値を使うことによってオブジェクトのデータ構造体 (再配置テーブルを含む) を取得する

  6. 実行時リンカーは、%g1 値をシフトしプロシージャのリンクテーブルエントリのサイズで除算することで、 name101 の再配置エントリのインデックスを計算する。再配置エントリ 101 はタイプ R_SPARC_JMP_SLOT を保持し、そのオフセットは .PLT101 のアドレスを指定し、また、そのシンボルテーブルインデックスは name101 を参照する。したがって、実行時リンカーはシンボルの実際の値を取得し、スタックを戻し、プロシージャのリンクテーブルエントリに変更を加え、本来の宛先に制御を渡す

実行時リンカーは、メモリーセグメント欄に示された命令シーケンスを必ずしも作成するとは限りません。ただし、作成する場合は、いくつかの点でより詳細な説明が必要です。


注 –

.PLT101.PLT102 の命令シーケンスの違いから、関連する宛先に合わせた最適化の方法を知ることができます。


LD_BIND_NOW 環境変数は、動的リンク動作を変更します。この環境変数の値がヌル文字列以外の場合、実行時リンカーは、プログラムに制御を渡す前に R_SPARC_JMP_SLOT 再配置エントリ (プロシージャのリンクテーブルエントリ) を処理します。

SPARC: 64 ビットプロシージャのリンクテーブル

64 ビット SPARC 動的オブジェクトの場合、プロシージャのリンクテーブルは専用データ内に存在します。実行時リンカーは、宛先の絶対アドレスを判定し、これらの絶対アドレスに従ってプロシージャのリンクテーブルのメモリーイメージに変更を加えます。

最初の 4 つのプロシージャのリンクテーブルエントリは、予約されています。表 7–49 で例示されてはいますが、これらのエントリの元の内容は指定されていません。テーブル内の先頭 32,768 エントリは、それぞれ 8 ワード (32 バイト) を占め、32 バイト境界で整列する必要があります。テーブル全体は 256 バイト境界で整列する必要があります。32,768 を超えるエントリが必要な場合、残りのエントリは 6 ワード (24 バイト) および 1 つのポインタ (8 バイト) で構成されます。 命令は、160 エントリのブロックにまとめられ、その次に 160 個ポインタが続きます。最後のグループのエントリとポインタは、160 未満でもかまいません。パディングの必要はありません。


注 –

32,768 および 160 という数字は、それぞれ分岐と読み込み置換の制限に基づいており、また、キャッシュの効率を向上させるために、コードとデータの間の区分を 256 バイト境界に合わせています。


再配置テーブルは、プロシージャのリンクテーブルに関連付けられています。_DYNAMIC 配列の DT_JMP_REL エントリは、最初の再配置エントリの位置を与えます。再配置テーブルには、予約されていないプロシージャのリンクテーブルエントリごとに 1 つのエントリが同じ順番で存在します。各エントリの再配置タイプは、R_SPARC_JMP_SLOT です。最初の 32,767 スロットでは、再配置オフセットは関連するプロシージャのリンクテーブルエントリの先頭バイトのアドレスを指定します。加数フィールドはゼロになります。シンボルテーブルインデックスは適切なシンボルを参照します。32,768 以後のスロットでは、再配置オフセットは関連するポインタの先頭バイトのアドレスを指定します。加数フィールドは、再配置されていない値 -(.PLTN + 4) になります。シンボルテーブルインデックスは適切なシンボルを参照します。

プロシージャのリンクテーブルの機能を説明するため、表 7–49 にエントリがいくつか示されています。最初の 3 つのエントリは、予約済みの初期エントリを示します。続く 3 つのエントリは、32,768 エントリの初期状態と、それぞれ、対象アドレスがエントリの +/- 2G バイト以内の場合、アドレス空間の下位 4G バイト以内の場合、およびその他の場合に適用されると考えられる、変換された状態を示しています。最後の 2 つのエントリは、命令とポインタのペアで構成される、後のエントリの例を示します。左欄は、動的リンクが行われる前のオブジェクトファイルの命令を示しています。右欄は、実行時リンカーがプロシージャのリンクテーブルエントリを変更した結果を示しています。

表 7–49 64-bit SPARC: プロシージャのリンクテーブルの例
オブジェクトファイルメモリーセグメント
.PLT0:
    unimp
    unimp
    unimp
    unimp
    unimp
    unimp
    unimp
    unimp
.PLT1:
    unimp
    unimp
    unimp
    unimp
    unimp
    unimp
    unimp
    unimp
.PLT2:
    unimp
.PLT0:
    save    %sp, -176, %sp
    sethi   %hh(runtime_linker_0), %l0
    sethi   %lm(runtime_linker_0), %l1
    or      %l0, %hm(runtime_linker_0), %l0
    sllx    %l0, 32, %l0
    or      %l0, %l1, %l0
    jmpl    %l0+%lo(runtime_linker_0), %o1
    mov     %g1, %o0
.PLT1:
    save    %sp, -176, %sp
    sethi   %hh(runtime_linker_1), %l0
    sethi   %lm(runtime_linker_1), %l1
    or      %l0, %hm(runtime_linker_1), %l0
    sllx    %l0, 32, %l0
    or      %l0, %l1, %l0
    jmpl    %l0+%lo(runtime_linker_0), %o1
    mov     %g1, %o0
.PLT2:
    .xword  identification
.PLT101:
    sethi   (.-.PLT0), %g1
    ba,a    %xcc, .PLT1
    nop
    nop
    nop;    nop
    nop;    nop
.PLT102:
    sethi   (.-.PLT0), %g1
    ba,a    %xcc, .PLT1
    nop
    nop
    nop;    nop
    nop;    nop
.PLT103:
    sethi   (.-.PLT0),  %g1
    ba,a    %xcc, .PLT1
    nop
    nop
    nop
    nop
    nop
    nop
.PLT101:
    nop
    mov     %o7,  %g1
    call    name101
    mov     %g1, %o7
    nop;    nop
    nop;    nop
.PLT102:
    nop
    sethi   %hi(name102), %g1
    jmpl    %g1+%lo(name102), %g0
    nop
    nop;    nop
    nop;    nop
.PLT103:
    nop
    sethi   %hh(name103), %g1
    sethi   %lm(name103), %g5
    or      %hm(name103), %g1
    sllx    %g1, 32, %g1
    or      %g1, %g5, %g5
    jmpl    %g5+%lo(name103), %g0
    nop
.PLT32768:
    mov     %o7, %g5
    call    .+8
    nop
    ldx     [%o7+.PLTP32768 -
              (.PLT32768+4)], %g1
    jmpl    %o7+%g1, %g1
    mov     %g5, %o7

    ...

.PLT32927:
    mov     %o7, %g5
    call    .+8
    nop
    ldx     [%o7+.PLTP32927 -
              (.PLT32927+4)], %g1
    jmpl    %o7+%g1, %g1
    mov     %g5, %o7
.PLT32768:
    <unchanged>
    <unchanged>
    <unchanged>
    <unchanged>

    <unchanged>
    <unchanged>

    ...

.PLT32927:
    <unchanged>
    <unchanged>
    <unchanged>
    <unchanged>

    <unchanged>
    <unchanged>
.PLTP32768
    .xword  .PLT0 -
              (.PLT32768+4)
    ...

.PLTP32927
    .xword  .PLT0 -
              (.PLT32927+4)
.PLTP32768
    .xword  name32768 -
              (.PLT32768+4)
    ...
    
.PLTP32927
    .xword  name32927 -
	      (.PLT32927+4)

実行時リンカーとプログラムは、以下の手順に従ってプロシージャのリンクテーブル内のシンボル参照を協調して解決します。ただし、以下に記述されている手順は、単に説明用のためのものです。実行時リンカーの正確な実行時動作については、記述されていません。

  1. 実行時リンカーは、プログラムのメモリーイメージを最初に作成するとき、プロシージャのリンクテーブルの初期エントリに、実行時リンカー自身のルーチンの 1 つに制御を渡すように変更を加える。実行時リンカーはまた、識別情報 (identification) の拡張ワードを 3 番目のエントリに格納する。実行時リンカーは、制御を受け取ると、この拡張ワードを調べることで、このルーチンを呼び出したオブジェクトを見つけることができる

  2. 他のすべてのプロシージャのリンクテーブルエントリは、最初、先頭または 2 番目のエントリに渡される。これらのエントリは、スタックフレームを確立して、実行時リンカーを呼び出す

  3. 実行時リンカーは、識別情報の値を使うことによってオブジェクトのデータ構造体 (再配置テーブルを含む) を取得する

  4. 実行時リンカーは、テーブルスロットの再配置エントリのインデックスを計算する

  5. インデックス情報に関しては、実行時リンカーはシンボルの実際の値を取得し、スタックを戻し、プロシージャのリンクテーブルエントリを変更してから、制御を宛先に渡す

実行時リンカーは、メモリーセグメント欄に示された命令シーケンスを必ずしも作成する必要はありません。ただし、作成する場合は、いくつかの点でより詳細な説明が必要です。

エントリの 2 番目のフォームに示すように、ポインタの変更は、単一の不可分 64 ビットストアを使用して行われます。


注 –

.PLT101.PLT102、および .PLT103 の命令シーケンスの違いから、関連する宛先に合わせた最適化の方法を知ることができます。


LD_BIND_NOW 環境変数は、動的リンク動作を変更します。この環境変数の値がヌル文字列以外の場合、実行時リンカーは、プログラムに制御を渡す前に R_SPARC_JMP_SLOT 再配置エントリ (プロシージャのリンクテーブルエントリ) を処理します。

x86: 32 ビットプロシージャのリンクテーブル

32 ビット x86 動的オブジェクトの場合、プロシージャリンクテーブルは共有テキスト内に存在しますが、非公開の大域オフセットテーブル内のアドレスを使用します。 実行時リンカーは、宛先の絶対アドレスを判定し、これらの絶対アドレスに従って大域オフセットテーブルのメモリーイメージに変更を加えます。このようにして実行時リンカーは、位置からの独立性とプログラムのテキストの共有性を低下させることなくエントリをリダイレクトします。実行可能ファイルと共有オブジェクトファイルには、別個のプロシージャのリンクテーブルが存在します。

表 7–50 x86: 絶対プロシージャのリンクテーブルの例
.PLT0:
    pushl   got_plus_4
    jmp     *got_plus_8
    nop;    nop
    nop;    nop
.PLT1:
    jmp     *name1_in_GOT
    pushl   $offset
    jmp     .PLT0@PC
.PLT2:
    jmp     *name2_in_GOT
    pushl   $offset
    jmp     .PLT0@PC

表 7–51 x86: 位置に依存しないプロシージャリンクテーブルの例
.PLT0:
    pushl   4(%ebx)
    jmp     *8(%ebx)
    nop;    nop
    nop;    nop
.PLT1:
    jmp     *name1@GOT(%ebx)
    pushl   $offset
    jmp     .PLT0@PC
.PLT2:
    jmp     *name2@GOT(%ebx)
    pushl   $offset
    jmp     .PLT0@PC


注 –

前述の例が示すとおり、プロシージャリンクテーブルの命令は、絶対コードと位置に依存しないコードで異なるオペランドアドレス指定モードを使用します。それでも、実行時リンカーへのインタフェースは同一です。


実行時リンカーとプログラムは、以下の手順に従ってプロシージャのリンクテーブル内と大域オフセットテーブル内のシンボル参照を協調して解決します。

  1. 実行時リンカーは、プログラムのメモリーイメージを最初に作成するとき、大域オフセットテーブルの 2 番目と 3 番目のエントリに特殊な値を設定する。これらの値については、以下の手順で説明する

  2. プロシージャのリンクテーブルが位置に依存していない場合、大域オフセットテーブルのアドレスは、%ebx に存在しなければならない。プロセスイメージにおける各共有オブジェクトファイルには自身のプロシージャのリンクテーブルが存在しており、制御は同じオブジェクトファイル内からのみプロシージャのリンクテーブルエントリに渡される。したがって、呼び出し側関数は、プロシージャのリンクテーブルエントリを呼び出す前に、大域オフセットテーブルベースレジスタをセットしなければならない

  3. たとえば、プログラムが name1 を呼び出すと、制御が .PLT1 に渡される

  4. 最初の命令は、name1 の大域オフセットテーブルエントリのアドレスにジャンプする。大域オフセットテーブルは最初は、後続の pushl 命令のアドレスを保持する (name1 の実アドレスは保持しない)

  5. プログラムは再配置オフセット (offset) をスタックにプッシュする。再配置オフセットは、再配置テーブルへの 32 ビットの負ではないバイトオフセット。指定された再配置エントリには R_386_JMP_SLOT が存在しており、オフセットは、前の jmp 命令で使用された大域オフセットテーブルエントリを指定する。再配置エントリにはシンボルテーブルインデックスも存在しており、実行時リンカーはこれを使って参照されたシンボル name1 を取得する

  6. プログラムは、再配置オフセットをプッシュした後、.PLT0 (プロシージャのリンクテーブルの先頭エントリ) にジャンプする。pushl 命令は、2 番目の大域オフセットテーブルエントリ (got_plus_4 または 4(%ebx)) の値をスタックにプッシュして、実行時リンカーに 1 ワードの識別情報を与える。プログラムは次に、3 番目の大域オフセットテーブルエントリ (got_plus_8 または 8(%ebx)) のアドレスにジャンプして、実行時リンカーにジャンプする

  7. 実行時リンカーはスタックを戻し、指定された再配置エントリを調べ、シンボルの値を取得し、name1 の実際のアドレスを大域オフセットテーブルエントリに格納し、そして宛先にジャンプする

  8. その後のプロシージャのリンクテーブルエントリに対する実行は、name1 に直接渡される (実行時リンカーの再呼び出しは行われない)。.PLT1 における jmp 命令は、pushl 命令にジャンプする代わりに、name1 にジャンプする

LD_BIND_NOW 環境変数は、動的リンク動作を変更します。この環境変数の値がヌル文字列以外の場合、実行時リンカーは、プログラムに制御を渡す前に R_386_JMP_SLOT 再配置エントリ (プロシージャのリンクテーブルエントリ) を処理します。