ELF(Executable and Linking Format)



ELFはUNIX System Laboratories (USL) によって開発され配布されたバイナリフォーマットで、3種類の物が有ります。

■種類

■実行可能ファイル

実 行に適したコードやデータを含んでいます。                                                                                                          

■リロケータブル(再配置可能)ファイル

他のリロケータブルあるいは共有オブジェクトファイルとリンクするのに適したコードおよびデータを含んでいます。
                                                                 

■共有オブジェクトファイル(共有ライブラリ)

リンク時におけるリンクエディターld および実行時におけるダイナミックリンカーに適したコードとデータを含んでいます。
ダイナミックリンカは実装に依存しますが、ld.so.1、libc.so.1、ld-linux.so.1などと呼ばれています。



■リンクとロードについて

■共有ライブラリ

共有ライブラリは、プログラム起動時にロードされるライブラリです。
コードの位置が独立しているオブジェクトファイルを生成し、そのオブジェクトファイルを束ねて作ります。

●ライブラリを更新しながらも、そのライブラリの古くて後方互換性のないバージョンを使いたいというプログラムを、引き続きサポートすることができる
●特定のプログラムを実行するとき、特定のライブラリ、もしくはライブラリ内の特定の関数でさえもオーバーライドすることができる
●既存のライブラリを使用してプログラムが動いている間にも、これら全てをおこなうことができる


■名前規約

共有ライブラリにはsonameとrealnameという二つの名前があります。
sonameは"lib"というサフィックス(最下層のCライブラリは除く)と、".so."とメジャーバージョンと呼ばれる番号を足したプレフィックスで構成されます。
例 : libABC.so.1 (メジャーバージョンが1)
realnameは実際のファイルネームになり、sonameに".マイナーバージョン番号.リリース番号"というプレフィックスを付けます。最後のピリオドとリリース番号は任意です。


■ディレクトリ

FHS(Filesystem Hierarchy Standard)によると、ほとんどのライブラリは /usr/lib にインストールし、起動時に必要とされるライブラリは /lib に、システムの一部ではないライブラリは /usr/local/lib にインストールするのが良いということになっています。


■ldconfig

上 記のディレクトリのうちの一つか、/etc/ld.so.confに記載されているディレクトリにライブラリをインストールし、ldconfigを実行す るとsonameのシンボリックリンクが同階層に作成され、キャッシュファイル/etc/ld.so.cache(etc配下ですが、バイナリーデータで す)を更新します。
ldconfigは、ライブラリインストール時にパッケージ管理ツールによっておこなわれることが多いです。


■ローダー

GNU glibcベースのシステム (全てのGNU/Linux) では、ELFバイナリ実行ファイルを起動すると、自動的にプログラムローダーがロードされ実行されます。GNU/Linuxでは、/lib/ld- linux.so.X(Xはバージョン番号)という名前です。ローダーによって、プログラムで使用されるその他の全ての共有ライブラリを順次探し出しロー ドします。
ローダーは/etc/ld.so.confと環境変数LD_LIBRARY_PATH、上記3つの標準的な場所から検索します。
ローダーに直接検索PATHと実行ファイルを与えて実行する事も出来ます。

/lib/ld-linux.so.2 --library-path $FIND_PATH $EXEC_FILE

LD_DEBUG環境変数を使うと、ローダーからdlopen系の関数のデバッグ情報を出力されるようになります。




■作り方

# GNU/Linuxの場合(多分Solarisも)
CC=g++
OBJ=a.o b.o
libmyGraphics.so.1.0.1:$(OBJ)
    $(CC) -shared -Wl,-soname,libmyGraphics.so.1 \
        -o libmyGraphics.so.1.0.1 $(OBJ) -lc
.c.o:
        $(CC) -O -g -c -fPIC -o $*.o $<

-Wlはその後に続く文をカンマを空白に変換してリンカーに渡します。



# 参考までにHP-UXの場合
CC=aCC
OBJ=a.o b.o
libmyGraphics.sl:$(OBJ)
    $(CC) -b -o libmyGraphics.sl -lc
.c.o:
        $(CC) -O -g -c +z -o $*.o $<



■使い方?

$ ln -s  libmyGraphics.so.1.0.1 libmyGraphics.so.1;#soname
$ ln -s  libmyGraphics.so.1 libmyGraphics.so;#linkername



■動的ライブラリ

動的ライブラリは、プログラム実行時にロードされるライブラリです。
プラグインやモジュールを実装するときに特に便利です。
ファイルフォーマット的には上記の共有ライブラリーや通常のオブジェクトファイルと同じです。
違いは、関数バインドをローダーをラップしたAPIで行います。

void * dlopen(const char *filename, int flag);

ライブラリーファイルを開きます。
filenameが絶対PATH以外の指定だった場合、LD_LIBRARY_PATH 環境変数、/etc/ld.so.cache、/lib、 /usr/libの順に検索します。
flagにはRTLD_LAZY(動的ライブラリのコードが実行されるときに未定義シンボルを解決せよ
)か、RTLD_NOW(リターンする前に全ての未定義シンボルを解決せよ、それができないようならば失敗せよ)を指定します。
RTLD_LAZY を使うと、未解決の参照があったときに不可解なエラーが生成されます。 RTLD_NOW を使うと、ライブラリのオープンには若干時間がかかるようになります (しかし、のちのちの検索速度は速くなります)。
ライブラリが他のライブラリに依存しているなら (例えば、X が Y に依存している)、依存されているほうを先にロードします(この例で言えば、Y を先にロードし、それから X をロードします)。

void * dlsym(void *handle, char *symbol);

シンボル解決をして関数ポインターを返します。
handleにはdlopenからの戻り値を渡し、symbolには関数名を書きます。


工事中






■■

■pic(position independent code)どこに配置されても動くコード

■一般的なobjectファイルの持つ要素
1)ヘッダー:コードのサイズ、ソースファイル名、作成日時などのファイル情報
2)オブジェクトコード:コードやデータなどのバイナリーデータ
3)再配置情報:リンカーがコードのアドレスを変更した時に調整が必要なオブジェクトコード内の位置のリスト
4)シンボル:内部にある大域シンボルや他からインポートするシンボルや、リンカーが定義するシンボル
5)デバッグ情報:ソースファイルの行番号やローカルシンボルや構造体定義やクラス定義




■ms-dos com
dosのcomファイルはバイナリーコード以外の情報を一切持ちません。
オフセット0x100(0xffまではパラメーター等をほぞんするPSP(プログラムセグメントプリフィックス))から始まるようにロードして実行しま す。
全てのセグメントレジスタがPSPを指すようにして、SP(スタックポインター)をセグメントとの終わりを指すようします。
セグメント内にプログラムが収まれば、アドレスの調整は必要ありませんが、収まらない場合はプログラマーが調整しなければなりません。

■ms-dos exe
struct exe_header_tag{
    char signature[2] = "MZ";
    short lastsize;
    short nblocks;
    short nreloc;
    short hdrsize;
    short minalloc;
    short maxalloc;
    void far *sp;
    short schecksum;
    void far *ip;
    short relocpos;
    short noverlay;
    char extra[];
    void far*relocs[];
}
※farはセグメント16bit、オフセット16bitの32bitアドレス

●起動シーケンス
1)マジックナンバーチェック
2)必要メモリー領域調査
3)PSP作成
4)PSPの直後にコードを読み込む
5)
6)




■a.out
struct a_out_header{
    int a_magic;    //マジックナンバー
    int a_text;     //テキストセグメントのサイズ
    int a_data;     //データセグメントのサイズ
    int a_bss;      //オブジェクト内には無いが、0で初期化されて必要な領域のサイズ。
    int a_syms;     //シンボルテーブルのサイズ
    int a_entry;    //プログラムの開始アドレス(リンクが完了した時点でデバッグ情報を入れなければ0が入れられる)
    int a_tresize;  //テキスト再配置情報のサイズ(リンクが完了した時点で0が入れられる)
    int a_dresize;  //データ再配置情報のサイズ(リンクが完了した時点で0が入れられる)
}
以前UNIXでよく使われていたオブジェクトファイルフォーマットで亜種もありますが、BSDの物が一般的のようです。
(UNIXではファイルセクションをセグメントと呼ぶ)

●起動シーケンス(NMAGIC手法)
1)ヘッダーを読んで各セグメントのサイズを調べる
2)共有可能コードセグメントが既に他のプロセスによって存在する場合は、自プロセスのアドレス空間に実行可能セグメントをマップする。無ければ、セグメントをアドレス空間に作成し、テキストセグメントをロードする。
3)データとbssを合わせて格納出来るプロセス固有のデータセグメントを作成してマップすし、データセグメントから初期値をロードし、bssを0初期化する。
4)スタックセグメントを作成してマップし、コマンド行や呼び出したプロセスからのパラメーターを置く。
5)レジスタを初期化して、開始アドレスにジャンプする

後にページングを使ってオブジェクトファイルをそのままマッピングする手法が使われ、セグメントをページ境界サイズに合わせ、テキストセグメント はRO(リードオンリー)、データセグメントはCOW(CopyOnWrite書き込み時コピー)でマップすることで頑健で起動が早く出来るようにしまし た。(ZMAGIC手法)
a.outヘッダーもページサイズになり、テキストセグメントもページサイズ境界に合わせたサイズになりましたが、データはbssと合わせるので、ロード時に調整されました。
ヘッダー部が数KBにもなってしまう為、後に(QMAGIC手法)ヘッダーもテキストセグメントに含まれ、開始アドレスをずらすことになりました。
これにより0番地は無効なアドレスとなり、0番地への参照はエラーになりました。


■a.outの再配置

[a.outの略図]
--------------------
ヘッダー
--------------------
テキストセクション
--------------------
データセクション
--------------------
テキスト再配置情報
--------------------
データ再配置情報
--------------------
シンボルテーブル
--------------------
文字列テーブル
--------------------

[再配置エントリの形式]
--------------------
アドレス(4バイト)
--------------------
インデックス(3バイト)
--------------------
pc rel(1ビット)
長さフィールド(2ビット)
externフラグ(1ビット)
予備(4ビット)
--------------------

アドレスは再配置項目のテキストセグメントかデータセグメンの先頭からのオフセットです。
長さは項目の長さで、0〜3でそれぞれ1,2,4バイトを表します。
pc relフラグは項目がプログラムカウンター相対を表し、命令内では相対アドレスとして扱われます。
externフラグがオフの場合、インデックスは項目がテキスト、データ、bssのどれかを示します。
externフラグがオンの場合、外部シンボルで有る事示し、インデックスはシンボルテーブルのシンボル番号を示します。

[シンボルエントリの形式]
--------------------
シンボル名の文字列テーブル内のオフセット(4バイト)(文字列はNULL止めされています)
--------------------
タイプ(1バイト)
--------------------
予備(1バイト)
--------------------
デバッガー情報(2バイト)
--------------------
値(4バイト)
--------------------

タイプの最下位ビットがオンで大域シンボルを表し、残りのビットで下記のようなタイプを示します。
●テキスト、データ、bss
モジュール内に定義されたシンボル。値はシンボルに対するモジュール内の再配置アドレス。
●abs
再配置されない絶対値シンボルで通常はデバッガー用情報に使われます。値はシンボルの絶対値になります。
●未定義
モジュール内に定義が無いシンボルで、大域シンボルでなければなりません。値は通常0です。

a.out形式は動的リンクやC++のサポートしていません。(.init, .finiというセクションが必要なります)




■ELF
typedef struct elf_header_TAG{
    char magic[4] = "\177ELF";  //マジックナンバー
    char addresssize;           //アドレスサイズ 1=32bit, 2=64bit
    char byteorder;             //バイトオーダー 1=リトルエンディアン, 2=ビッグエンディアン
    char hversion;              //ヘッダーバージョン(常に1)
    char pad[9];                //
    short filetype;             //ファイルタイプ 1=再配置可能, 2=実行可能, 3=共有オブジェクト, 4=コアイメージ
    short archtype;             //アーキテクチュアタイプ 2=SPARC, 3=x86, 4=68k
    int fversion;               //ファイルバージョン(常に1)
    int entry;                  //実行可能のファイルの時はエントリーポイント
    int phdrops;                //プログラムヘッダーのファイル内の位置、または0
    int shdrops;                //セクションヘッダーのファイル内の位置、または0
    int flags;                  //アーキテクチュア固有のフラグ、通常は0
    short hdrsize;              //このELFヘッダーのサイズ
    short phdrent;              //プログラムヘッダーの一つのエントリーのサイズ
    short hpdrcnt;              //プログラムヘッダーのエントリーの個数、または0
    short shdrent;              //セクションヘッダーの一つのエントリーのサイズ
    short phdrcnt;              //セクションヘッダーのエントリーの個数、または0
    short strsec;               //セクション名文字列を格納しているセクションの番号
} ELF_HEADER;

typedef struct section_header_TAG{
    int sh_name;     //文字列テーブルのインデックス
    int sh_type;     //セクションタイプ
    int sh_flags;    //フラグビット
    int sh_addr;     //ロード可能な時はベースメモリアドレス、それ以外は0
    int sh_offset;   //セクションの先頭のファイル内のオフセット
    int sh_size;     //バイト位置
    int sh_link;     //関連する情報を持つセクションの番号、または0
    int sh_info;     //セクション固有の情報
    int sh_align;    //セクションを移動する場合の整列の粒度
    int sh_entsize;  //セクションが配列の場合のエントリーのサイズ
} SECTION_HEADER;

SECTION_HEADER sectionHeaderTable[numOfSection];

typedef struct symbole_entry_TAG{
    int name;      //文字列テーブル内の名前の位置
    int value;     //シンボルの値(再配置可能な場合はセクションに相対、実行可能な場合は絶対)
    int size;      //オブジェクトまたは関数のサイズ
    char type;     //データオブジェクト、関数、セクションシンボル、またはファイル(特例)
    char bind;     //local,global,weak
    char other;    //予備
    short sect;    //セクション番号、ABS,SOMMON,UNDEF
} SYMBOLE_ENTRY


■実行可能ファイル
実行に適したコードやデータを含んでいます。
再配置が全て完了していて、実行時に解決される共有ライブラリのシンボルを除いて全てのシンボルが解決済なファイルです。

■リロケータブル(再配置可能)ファイル
他のリロケータブルあるいは共有オブジェクトファイルとリンクするのに適したコードおよびデータを含んでいます。
コンパイラやアセンブラが作成しますが、リンクをしなければ実行できないファイルです。

■共有オブジェクトファイル(共有ライブラリ)
リンク時におけるリンクエディターld および実行時における ダイナミックリンカーに適したコードとデータを含んでいます。
ダイナミックリンカは実装に依存しますが、ld.so.1、libc.so.1、ld-linux.so.1などと呼ばれています。
リンカー用のシンボル情報と実行時にそのまま使われるコードの両方を格納しています。

[ELFファイルの形式]
--------------------------------------
ELFヘッダー
--------------------------------------
プログラムヘッダーのテーブル(セグメントヘッダーのテーブル)
コンパイル時やリンク時は無視されます。コンパイル時やリンク時は省略可能です。
--------------------------------------
.text (コード)
PROGBITS,ALLOC+EXECINSTR
--------------------------------------
.data (データ)
PROGBITS,ALLOC+WRITE
--------------------------------------
.rodata (リードオンリーデータ)
PROGBITS,ALLOC
--------------------------------------
.bss (0初期化され領域確保されるデータ)
NOBITS,ALLOC+WRITE
--------------------------------------
.symtab (シンボルテーブル)
SYMTAB
--------------------------------------
.dynsym (動的シンボルテーブル)
DYNSYM,ALLOC
--------------------------------------
.rel.text (コードの再配置情報)
REL|RELA,
--------------------------------------
.rel.data (データの再配置情報)
REL|RELA,
--------------------------------------
.rel.rodata (リードオンリーデータの再配置情報)
REL|RELA,
--------------------------------------
.rel.init (グローバルコンストラクターの再配置情報)
REL|RELA,
--------------------------------------
.rel.fini (グローバルデストラクターの再配置情報)
REL|RELA,
--------------------------------------
.init (グローバルコンストラクター用)
PROGBITS,ALLOC+EXECINSTR
--------------------------------------
.fini (グローバルデストラクター用)
PROGBITS,ALLOC+EXECINSTR
--------------------------------------
.line (デバッグ用の行番号とコードのマッピング情報)
PROGBITS,ALLOC
--------------------------------------
.debug (デバッグ用シンボル)
PROGBITS,ALLOC
--------------------------------------
.strtab (シンボルやセクションの名前の文字列)
STRTAB
--------------------------------------
.dynstr (動的シンボルの名前の文字列)
STRTAB,ALLOC
--------------------------------------
.got (グローバルオフセットテーブル)

--------------------------------------
.plt (Procedure Linkage Table 動的リンクに使用)

--------------------------------------
.comment (説明用文字列 バージョン管理番号として使われる事が多い)

--------------------------------------
.interp (インタープリタとして使われるプログラムの名前 一意でこのセクションが有った場合ファイルを実行せずにインタープリタにファイルを渡す)

--------------------------------------
セクションヘッダーのテーブル
実行時は無視されます。実行時は省略可能です
--------------------------------------

コンパイラ、アセンブラ、リンカーは、セクションヘッダーのテーブルを見て".text"〜".strtab"を論理セクションの集合体として扱います。
ローダーは、セグメントヘッダーのテーブルを見てセグメントの集合体としてELFファイルを扱います。
一つのセグメントは一個以上ののセクションで構成されています。
「ロード可能で読み出し専用」セグメントは、実行可能コード、読み出し専用データ、動的リンカー用シンボルを格納出来ます。
再配置可能ファイルにはセクションテーブルが有り、実行可能ファイルにはセグメントテーブルが有り、共有ライブラリーには両方があります。
セクションはリンカーの処理のために有り、セグメントはメモリへのマップの為にあります。

●●セクションタイプ
●PROGBITS:コード、データ、デバッグ情報などのプログラムの内容
●NOBITS:ファイル自体に割り付けられていないもの。bss
●SYMTAB:全てのシンボルテーブル
●DYNSYM:実行時にダイナミックリンクに使われるシンボルテーブル
●STRTAB:文字列テーブル。a.outと違いセクション名、シンボル名、動的シンボル名とタイプによって独立したテーブルを持つ事が多いです。
●REL:コードやデータの格納されているベース値に再配置値を加算します。x86で使われます。
●RELA:再配置用ベース値を再配置エントリーに格納して使います。68kで使われます。
●DYNAMIC:動的リンク情報
●HASH:実行時のシンボルハッシュテーブル

●●セクションヘッダー フラグタイプ
●ALLOC:プログラムロード時にそのセクション用のメモリが必要です。
●WRITE:ロードしたセクションが書き込み可能です。
●EXECINSTR:実行可能コードがセクションに含まれています。

●シンボルテーブルのエントリー
シンボルのエントリーにはa.outに幾つかの要素を足したような感じになっています。
sizeはオブジェクトのサイズを表し、bssでの領域確保にも使われます。
シンボルのバインド方法にはlocal,global,weakがあり、weakはglobalのように扱われ、未定義のweakシンボルの定義 が見付かればバインドし、見つからなければ、デフォルト値の0を使います。(weakシンボルはstubルーチンの手間を省くのに使われます)
typeは通常はデータか関数です。





struct program_header_TAG{
    int type;      //ロード可能なコードまたはデータ、動的リンク情報
    int offset;    //セグメントのファイル内のオフセット
    int virtaddr;  //セグメントのマップ先仮想アドレス
    int physaddr;  //物理アドレス(未使用)
    int filesiza;  //ファイル内のセグメントのサイズ
    int memsize;   //メモリ内のセグメントサイズ
    int flags;     //読み出し書き込み実行の各ビット
    int align;     //整列サイズ(ハードのページサイズ)
} PROGRAM_HEADER;

PROGRAM_HEADER programHeaderTable[numOfSegment];

ELFヘッダーとプログラムヘッダーはテキストセグメントとして扱います。
読み書き可能なデータセグメントはテキストセグメントの後に始まります。



[ELFロード可能セグメント]
ファイルオフセット    ロードアドレス    ファイルサイズ    メモリサイズ
----------------------------------------------------------------------------------------------------------------
ELFヘッダー                 0             0x8000000
プログラムヘッダー         0x40           0x8000040
テキスト                   0x100          0x8000100           0x4500
データ                     0x4600         0x8005600           0x2200            0x3500





●gotはデータセグメントの先頭になる。PICの実装に使う。
●PLT(procedure linkage table)は関数を使用時にバインドする遅延評価に使われる。テキストセグメントの最後尾に付ける。











工事中



戻 る