リンカーとライブラリ

リンカーのサポートインタフェース

リンカーは、ファイルのオープンやこれらのファイルからのセクションの連結を含む多数の操作を実行します。これらの操作の監視、および場合によっては変更は、コンパイルシステムのコンポーネントにとって有益なことがよくあります。

この節では、「ld-サポート」インタフェースについて説明します。このインタフェースは、入力ファイル検査用、およびリンク編集を構成するファイルの入力ファイルデータ変更用にもある程度サポートされています。このインタフェースを使用する 2 つのアプリケーションは、リンカーおよび make(1S) ユーティリティーです。リンカーは、このインタフェースを使用して再配置可能オブジェクト内のデバッグ情報を処理します。make ユーティリティーは、このインタフェースを使用して状態情報を保存します。

「ld-サポート」インタフェースは、1 つまたは複数のサポートインタフェースルーチンを提供するサポートライブラリから構成されています。このライブラリはリンク編集プロセスの一部として読み込まれます。ライブラリで検出されたサポートルーチンはすべてリンク編集の各段階で呼び出されます。

このインタフェースを使用するには、elf(3ELF) 構造とファイル形式に精通している必要があります。

サポートインタフェースの呼び出し

リンカーは、SGS_SUPPORT 環境変数またはリンカーの -S オプションのどちらかによって提供される 1 つまたは複数のサポートライブラリを受け入れます。環境変数は、コロンで区切られたサポートライブラリのリストから構成されています。


$ SGS_SUPPORT=./support.so.1:support.so.2 cc ...

-S オプションは、単一のサポートライブラリを指定します。複数の -S オプションを指定できます。


$ LD_OPTIONS="-S./support.so.1 -Ssupport.so.2" cc ...

サポートライブラリは、共有オブジェクトの 1 つです。リンカーは、dlopen(3C) を使用して、各サポートライブラリを指定された順序で開きます。環境変数と -S オプションの両方がある場合は、環境変数によって指定されたサポートライブラリが最初に処理されます。次に、各サポートライブラリ内で、dlsym(3C) を使用してサポートインタフェースルーチンの検索が実行されます。これらのサポートルーチンは、リンク編集の各段階で呼び出されます。

サポートライブラリは、32 ビットまたは 64 ビットのいずれの場合でも、呼び出されるリンカーの ELF クラスと一致している必要があります。詳細は、「32 ビットおよび 64 ビット環境」を参照してください。


注 –

デフォルトでは、リンカーは Solaris OS サポートライブラリ libldstab.so.1 を使用して、入力再配置可能オブジェクト内に提供されるコンパイラ生成デバッグ情報を処理、圧縮します。このデフォルト処理は、-S オプションを使用して指定されたサポートライブラリでリンカーを呼び出すと抑止されます。サポートライブラリサービスだけでなく libldstab.so.1 のデフォルト処理も必要な場合があります。その場合は、リンカーに提供されたサポートライブラリのリストに libldstab.so.1 を明示的に追加します。


32 ビットおよび 64 ビット環境

「32 ビットおよび 64 ビット環境」で説明しているように、64 ビットリンカー ld(1) は 32 ビットのオブジェクトを生成できます。また、32 ビットリンカーは 64 ビットのオブジェクトを生成できます。これらのオブジェクトはそれぞれ、定義されているサポートインタフェースに関連付けられています。

64 ビットオブジェクトのサポートインタフェースは 32 ビットオブジェクトのインタフェースと似ていますが、末尾に「64」という接尾辞が付きます。たとえば、ld_start() および ld_start64() のようになります。この規則により、サポートインタフェースの両方の実装状態を、単一の共有オブジェクトの 32 ビットと 64 ビットの各クラスに常駐させることができます。

SGS_SUPPORT 環境変数は、接尾辞 _32 または _64 を使用して指定でき、また、リンカーオプション -z ld32 および -z ld64 を使用して -S オプション要件を定義できます。これらの各定義は、対応する 32 ビットまたは 64 ビットのリンカーによってのみ解釈されます。このため、リンカーの種類が不明な場合に、両方の種類のサポートライブラリを指定できます。

サポートインタフェース関数

「ld-サポートインタフェース」はすべて、ヘッダーファイル link.h に定義されています。インタフェース引数はすべて、基本的な C タイプまたは ELF タイプです。 ELF データタイプは、ELF アクセスライブラリ libelf を使用して確認できます。libelf の詳細は、elf(3ELF) のマニュアルページを参照してください。次のインタフェース関数が「ld-サポート」インタフェースにより提供されます。各インタフェース関数は、使用順序に従って記載されています。

ld_version()

この関数は、リンカーとサポートライブラリとの間の初期ハンドシェークを提供します。

uint_t ld_version(uint_t version);

リンカーは、リンカーがサポート可能な最新バージョンの「ld-サポート」インタフェースを使用して、このインタフェースを呼び出します。サポートライブラリは、このバージョンが使用するのに十分かどうかを確認できます。次に、サポートライブラリは、サポートライブラリが使用する予定のバージョンを返すことができます。通常、このバージョンは LD_SUP_VCURRENT です。

サポートライブラリがこのインタフェースを提供しない場合、初期サポートレベルは LD_SUP_VERSION1 と見なされます。

サポートライブラリがゼロのバージョン、またはリンカーがサポートする ld-サポートインタフェースよりも大きい値を返す場合、サポートライブラリは使用されません。

ld_start()

この関数は、リンカーコマンド行の初期妥当性検査のあとに呼び出されます。この関数は、入力ファイル処理の開始を示します。

void ld_start(const char * name, const Elf32_Half type,
        const char * caller);

void ld_start64(const char * name, const Elf64_Half type,
        const char * caller);

nameは、作成される出力ファイル名を示します。type は出力ファイルタイプであり、ET_DYNET_RELET_EXEC のいずれかで、これは sys/elf.h に定義されています。caller はインタフェースを呼び出すアプリケーションを示し、これは通常、/usr/ccs/bin/ld です。

ld_open()

この関数は、リンク編集への各入力ファイルに対して呼び出されます。バージョン LD_SUP_VERSION3 で追加されたこの関数は、ld_file() 関数よりも高い柔軟性を備えています。サポートライブラリはこの関数を使用することで、ファイル記述子、ELF 記述子、およびそれらに関連付けられたファイル名を置き換えることができます。この関数は次のシナリオで使用できます。

  • 既存の ELF ファイルへの新しいセクションの追加。この場合、元の ELF 記述子を、ELF ファイルの更新を可能とする記述子で置き換えるようにしてください。elf_begin(3ELF)ELF_C_RDWR 引数を参照してください。

  • 入力ファイルの全体を別のファイルで置き換え可能。この場合、元のファイル記述子と ELF 記述子を、新しいファイルに関連付けられた記述子で置き換えるようにしてください。

どちらのシナリオの場合も、パス名とファイル名を別の名前で置き換えることができ、そうした場合、それは入力ファイルが変更されたことを示します。

void ld_open(const char ** pname, const char ** fname, int * fd,
	int flags, Elf ** elf, Elf * ref, size_t off, Elf_Kind kind);
void ld_open64(const char ** pname, const char ** fname, int * fd,
	int flags, Elf ** elf, Elf * ref, size_t off, Elf_Kind kind);

pname は、処理されようとしている入力ファイルのパス名です。fname は、処理されようとしている入力ファイルのファイル名です。fname は通常、pname のベース名になります。pnamefname はどちらも、サポートライブラリから変更できます。

fd は、入力ファイルのファイル記述子です。サポートライブラリからこの記述子を閉じ、新しいファイル記述子をリンカーに返せます。値が -1 のファイル記述子を返せば、そのファイルを無視すべきであることを示せます。


注 –

ld_open() に渡された fd には、リンカーがld_open() でファイル記述子を閉じることができないと、値 -1 が設定されます。この状況が発生するもっとも一般的な理由は、アーカイブメンバーの処理の場合です。値 -1ld_open() に渡されると、記述子を閉じることができなくなり、代替の記述子がサポートライブラリから返されなくなります。


flags フィールドは、リンカーによるファイルの取得方法を示します。このフィールドには、次の定義の 1 つまたは複数を指定できます。

  • LD_SUP_DERIVED – ファイル名がコマンド行に明示的に指定されませんでした。ファイルは、-l を展開して派生されました。あるいは、ファイルは、抽出されたアーカイブメンバーです。

  • LD_SUP_EXTRACTED – ファイルはアーカイブから抽出されました。

  • LD_SUP_INHERITED – ファイルはコマンド行の共有オブジェクトの依存関係として取得されました。

flags 値が指定されていない場合は、入力ファイルがコマンド行に明示的に指定されました。

elf は、入力ファイルの ELF 記述子です。サポートライブラリからこの記述子を閉じ、新しい ELF 記述子をリンカーに返せます。値が 0 の ELF 記述子を返すことができ、そのファイルを無視すべきであることを示せます。elf 記述子がアーカイブライブラリのメンバーに関連付けられている場合、ref 記述子はその背後のアーカイブファイルの ELF 記述子になります。off は、アーカイブファイル内のアーカイブメンバーのオフセットを表します。

kind は入力ファイルのタイプを示し、libelf.h に定義されているように ELF_K_AR または ELF_K_ELF のいずれかになります。

ld_file()

この関数は、リンク編集への各入力ファイルに対して呼び出されます。この関数は、ファイルデータの処理が実行される前に呼び出されます。

void ld_file(const char * name, const Elf_Kind kind, int flags,
        Elf * elf);

void ld_file64(const char * name, const Elf_Kind kind, int flags,
        Elf * elf);

name は処理される入力ファイルを示します。kind は入力ファイルのタイプを示し、libelf.h に定義されているように ELF_K_AR または ELF_K_ELF のいずれかになります。flags フィールドは、リンカーによるファイルの取得方法を示します。このフィールドには、ld_open()flags フィールドと同じ定義を含めることができます。

  • LD_SUP_DERIVED – ファイル名がコマンド行に明示的に指定されませんでした。ファイルは、-l を展開して派生されました。あるいは、ファイルは、抽出されたアーカイブメンバーです。

  • LD_SUP_EXTRACTED – ファイルはアーカイブから抽出されました。

  • LD_SUP_INHERITED – ファイルはコマンド行の共有オブジェクトの依存関係として取得されました。

flags 値が指定されていない場合は、入力ファイルがコマンド行に明示的に指定されました。

elf は、入力ファイルの ELF 記述子です。

ld_input_section()

この関数は、入力ファイルの各セクションに対して呼び出されます。この関数は、リンカーがそのセクションを出力ファイルに送信することを決定する前に呼び出されます。これは、バージョン LD_SUP_VERSION2 で追加された関数です。これは、出力ファイルに寄与するセクションに対してのみ呼び出される、ld_section() 処理とは異なります。

void ld_input_section(const char * name, Elf32_Shdr ** shdr,
        Elf32_Word sndx, Elf_Data * data, Elf * elf, unit_t flags);

void ld_input_section64(const char * name, Elf64_Shdr ** shdr,
        Elf64_Word sndx, Elf_Data * data, Elf * elf, uint_t flags);

name は、入力セクション名を示します。shdr は、関連のセクションヘッダーへのポインタを示します。sndx は、入力ファイル内のセクションインデックスです。data は、関連データバッファーへのポインタを示します。elf は、ファイル ELF 記述子へのポインタです。flags は、将来の使用のために予約されています。

セクションヘッダーの再割り当ておよび *shdr への代入によるセクションヘッダーの変更は許されています。リンカーは、ld_input_section() から戻った後で、*shdr が指し示すセクションヘッダー情報を使用して、セクションを処理します。

データを再割り当てし、Elf_Data バッファーの d_buf ポインタに代入してデータを変更できます。データを変更する場合、Elf_Data バッファーの d_size 要素を正しく設定しなければなりません。出力イメージの一部になる入力セクションでは、d_size 要素をゼロに設定すると、出力イメージからデータが実際に削除されます。

flags フィールドは、初期値にゼロが設定される uint_t データフィールドを指します。フラグは、将来のアップデートでリンカーやサポートライブラリが割り当てできるように提供はされていますが、現在のところは割り当てられていません。

ld_section()

この関数は、出力ファイルに送信される入力ファイルのセクションごとに呼び出されます。この関数は、セクションデータの処理が実行される前に呼び出されます。

void ld_section(const char * name, Elf32_Shdr * shdr,
        Elf32_Word sndx, Elf_Data * data, Elf * elf);

void ld_section64(const char * name, Elf64_Shdr * shdr,
        Elf64_Word sndx, Elf_Data * data, Elf * elf);

name は、入力セクション名を示します。shdr は、関連のセクションヘッダーへのポインタを示します。sndx は、入力ファイル内のセクションインデックスです。data は、関連データバッファーへのポインタを示します。elf は、ファイル ELF 記述子へのポインタです。

データを再割り当てし、Elf_Data バッファーの d_buf ポインタに代入してデータを変更できます。データを変更する場合、Elf_Data バッファーの d_size 要素を正しく設定しなければなりません。出力イメージの一部になる入力セクションでは、d_size 要素をゼロに設定すると、出力イメージからデータが実際に削除されます。


注 –

出力ファイルから取り除かれるセクションは、ld_section() に報告されません。セクションは、リンカーの -s オプションを使って取り除かれます。セクションは、SHT_SUNW_COMDAT 処理や SHF_EXCLUDE の識別によって破棄されます。「「COMDAT」セクション」表 7–8 を参照してください。


ld_input_done()

この関数は、入力ファイルの処理が完了してから、出力ファイルの配置が実行されるまでに呼び出されます。これは LD_SUP_VERSION2 で追加された関数です。

void ld_input_done(uint_t * flags);

flags フィールドは、初期値にゼロが設定される uint_t データフィールドを指します。フラグは、将来のアップデートでリンカーやサポートライブラリが割り当てできるように提供はされていますが、現在のところは割り当てられていません。

ld_atexit()

この関数は、リンク編集の完了時に呼び出されます。

void ld_atexit(int status);

void ld_atexit64(int status);

status は、リンカーによって返される exit(2) コードであり、stdlib.h に定義されているように、EXIT_FAILURE または EXIT_SUCCESS のいずれかになります。

サポートインタフェースの例

次の例では、32 ビットリンク編集の一部として処理される再配置可能オブジェクトファイルのセクション名を出力するサポートライブラリを作成します。


$ cat support.c
#include        <link.h>
#include        <stdio.h>
 
static int      indent = 0;
 
void
ld_start(const char * name, const Elf32_Half type,
    const char * caller)
{
        (void) printf("output image: %s\n", name);
}
 
void
ld_file(const char * name, const Elf_Kind kind, int flags,
    Elf * elf)
{
        if (flags & LD_SUP_EXTRACTED)
                indent = 4;
        else
                indent = 2;
 
        (void) printf("%*sfile: %s\n", indent, "", name);
}
 
void
ld_section(const char * name, Elf32_Shdr * shdr, Elf32_Word sndx,
    Elf_Data * data, Elf * elf)
{
        Elf32_Ehdr *    ehdr = elf32_getehdr(elf);
 
        if (ehdr->e_type == ET_REL)
                (void) printf("%*s   section [%ld]: %s\n", indent,
                    "", (long)sndx, name);
}

このサポートライブラリは、libelf に依存して、入力ファイルタイプを判定するために使用される ELF アクセス関数 elf32_getehdr(3ELF) を提供します。このサポートライブラリを構築するには次のようにします。


$ cc -o support.so.1 -G -K pic support.c -lelf -lc

次の例は、再配置可能オブジェクトおよびローカル範囲アーカイブライブラリによる簡易アプリケーションの構築の結果生じたセクション診断を示しています。-S オプションを使用すると、デフォルトデバッグ情報処理だけでなく、サポートライブラリの呼び出しも行われます。


$ LD_OPTIONS=-S./support.so.1 cc -o prog main.c -L. -lfoo

output image: prog
  file: /opt/COMPILER/crti.o
     section [1]: .shstrtab
     section [2]: .text
     .......
  file: /opt/COMPILER/crt1.o
     section [1]: .shstrtab
     section [2]: .text
     .......
  file: /opt/COMPILER/values-xt.o
     section [1]: .shstrtab
     section [2]: .text
     .......
  file: main.o
     section [1]: .shstrtab
     section [2]: .text
     .......
  file: ./libfoo.a
    file: ./libfoo.a(foo.o)
       section [1]: .shstrtab
       section [2]: .text
       .......
  file: /lib/libc.so
  file: /opt/COMPILER/crtn.o
     section [1]: .shstrtab
     section [2]: .text
     .......

注 –

この例で表示されるセクションの数は、出力を簡素化するために減らされています。また、コンパイラドライバによって取り込まれるファイルも異なる場合があります。