空飛ぶ木造船

借り物ばかりの備忘録です。意味のあるものになると嬉しいです。

ELFの構造と種類

低レベルプログラミングの5.3.1から5.4の内容をまとめた.

ELFファイルの種類

ELF(Excutable and Linkable Format:実行とリンクが可能なフォーマット)は,*nixシステムのオブジェクトファイルとして典型的なフォーマットである.ELFは以下の三種類のファイルをサポートする.

  1. 再配置可能なオブジェクトファイル(relocatable object file)
    コンパイラが作成するオブジェクトファイル。 再配置(relocation)は,プログラムの各部に決定的なアドレスを割り当て,全てのリンクが適切に解決されるようにプログラムのコードを変更する処理のことである.つまり,絶対アドレスによってあらゆる種類のメモリアクセスを解決する.再配置は,プログラムを構成する複数のモジュールが互いを参照している時に必要になる.モジュール別のファイルでは,メモリに置かれる順序が固定されていないから,絶対アドレスはまだ決まらない.リンカがこれらのファイルを結合して実行可能なオブジェクトファイルを生成する.

  2. 実行可能なオブジェクトファイル(excuteble object file)
    そのままメモリにロードして実行できるファイル. これは基本的に,コードとデータとユーティリティ情報を構造化したストレージである.

  3. 共有オブジェクトファイル(shared object file)
    必要な時にメインプログラムがロードできるファイル. 動的にリンクされるので,動的ライブラリとも呼ばれWindows OSでは .dll ファイルとして知られている.*nixシステムでは,ファイル名が .so (shared object)で終わることが多い.

リンカの役割

あらゆるリンカの目的は,再配置可能なオブジェクトファイルの集合を受け取って,一個の実行可能な(あるいは共有)オブジェクトファイルを作成すること.そのためにリンカは次の仕事を行う.

  • 再配置(relocation)
  • シンボルの解決(symbol resolution)
    シンボル(関数や変数の名前)が参照されるたびに,リンカはオブジェクトファイルを変更して,命令オペランドのアドレスに対応する部分を正しい値で埋める必要がある.

ELFの構造

リンカから見たビュー(linking view)

セクションによって構成される. これを記述した「セクションヘッダ」の表はreadelf -Sで観察することができる.個々のセクションは次のうちのいづれかである.

  • メモリにロードされる生のデータ(raw data)
  • 他のセクションに関する整形されたメタデータ
    ローダが使う情報(ex: .bss),リンカが使う情報(ex: 再配置表),デバッガが使う情報(ex: .line)がある.

コードとデータはセクションの中に格納される.

ローダから見たビュー(excution view)

セグメントによって構成される. これを記述する「プログラムヘッダ」の表をreadelf -lで観察することがきる.この表のエントリには次の記述が入る.

  • システムがプログラムを実行するために必要となる情報
  • 0個以上の「プログラムヘッダ」セクションを含むELFセグメント
    プログラムヘッダは仮想メモリと同じパーミッション集合を持つ.それぞれ開始アドレスをもつ複数のセグメントが,連続的なページで構成される別々のメモリ領域にロードされる

ELFファイルのセクション

具体的なセクションの一部を挙げる.

  • .text: マシン語命令が入る
  • .rodata: read onlyデータが入る
  • .data: 初期化されたグローバル変数が入る
  • .bss: 読み書き可能なグローバル変数が0で初期化される(=未定義)
    全てがゼロ埋めされているため,その内容をオブジェクトファイルにダンプする必要はない.その代わりセクションの合計サイズが格納される(そのメモリをゼロにする高速な方法はOSが知っているだろう).アセンブリ言語ではresb,reswなどのディレクティブをsection .bssの後に置くことでこのセクションにデータを入れることができる.
  • .rel.text: .textのために再配置表が入る
    このテーブルはリンカのためにあり,このオブジェクトファイル専用のローディングアドレスを選択した後で.textを書き換えなければいけない場所を記録する目的で使われる.
  • .rel.data: モジュール内で参照されているデータのための再配置表が入る
  • .debug: プログラムをデバッグするためのシンボルテーブルが入る
    もしCやC++で書かれたプログラムなら,グローバル変数についての情報だけでなく(これらは.symtabに入る)ローカル変数についての情報もここに入る.
  • .line: コードの断片とソースコード内の行番号の対応を定義する
    高級言語ソースコードにおける行と,アセンブリ命令との対応が単純ではないのでこれが必要なる.
  • .strtab: 文字列が配列のように格納される
    他のセクションは文字列を直接使うのではなく, .strabのインデックスを使う
  • .symtab: シンボルテーブルが入る
    プログラマがラベルを定義したら必ずNASMがシンボルを作る.この表にはユーティリティ情報も含まれる.

ライブラリ

ライブラリには動的と静的の二種類がある.

静的ライブラリ

再配置可能なオブジェクトファイルから構成される.これらはメインプログラムにリンクされて実行ファイルに組み込まれる.静的ライブラリの実態はエントリポイントがなく中途半端な実行ファイルである. Windowsの世界ではファイルには.libという拡張子がつく.Unixの世界では.oファイルかさもなくば複数の.oファイルを内部にもつアーカイブの.aが用いられる.

動的ライブラリ

共有オブジェクトファイルとも呼ばれる.これがプログラムにリンクされるのはそのプログラムの実行中である. Windowsの世界で評判が悪い.dllファイルがこれである.Unixの世界では .so という拡張子がつく. 必要になったタイミングでロードされる.ライブラリ自身が完全なオブジェクトファイルなので,外部でライブラリの機能が使えるようにどんなコードを提供するかを調べるために必要なメタ情報を持っている.この情報をローダが使ってエクスポートされている関数とデータの正確なアドレスを決定する. プログラムは共有ライブラリをいくつでも使うことができるが,アドレスが衝突しないようにするために以下の二つの方策が取られる.

  • 実行時(ライブラリをロードしている時)に再配置を行う
    可能だが,そうした場合に複数のプロセスで同じライブラリを重複して読み込むことになってしまう. また, .dataは書き換え可能であるため再配置を行う必要がある.これを回避するためにはグローバル変数を撤廃する必要がある. また,再配置の際に.textを書き換える必要があるためこのセクションに書き込みを許可する必要がありセキュリティ上のリスクが生じる.さらに, ある実行ファイルが複数のライブラリを必要とする時には全ての共有オブジェクトについて.textを書き換えることになり時間がかかってしまう.

  • PIC(Position Independent Code:位置に依存しないコード)を書く
    rip相対アドレッシングを活用することで絶対アドレスを排除することで,メモリ上のどこに配置されても実行可能なコードを書くことができる.これにより.textセクションを複数のプロセスで共有することができる.

動的ライブラリを使うことで,複数のプロセスでコードの一部を共有することができディスクとメモリの節約になる.

動的ライブラリに特有のセクション

  • .dynsym: ライブラリの外にみせるシンボルが入る
  • .hash: ハッシュ表. .dynsymでシンボルをサーチする時間を短縮できる
  • .Fdynstr: 文字列群が入る.これらをdynsymからインデックスで参照する.