(Executable and Linkable Format という名前なだけあって)。
オブジェクトファイルを出力できると分割コンパイルができるようになって便利だろう、ということで調べてみた。
オブジェクトファイルの構成
実行形式のELFは、ELFヘッダに加えてプログラムヘッダを出力し、コードとデータを出力するだけでよかった。
オブジェクトファイル形式ではコードやデータ以外にリンクに必要な情報として、どういうラベルが定義されているかや参照しているかという情報が必要になる。 それらを表現するために、
- シンボルテーブル:どういうシンボルが定義されているか(または参照するか)
- 文字列テーブル:シンボル名(やセクション名)
- 再配置テーブル:外部のシンボルを参照する箇所の置き換え情報
そしてこれらのテーブルがELFファイルのどの範囲に出力されているか、というのをセクションヘッダで表している。
オブジェクトファイル形式の例
例えば定数を返すだけの関数:
int main() { |
をgccを使ってオブジェクトファイルにコンパイルして内容を見てみる。
セクションヘッダ
セクションヘッダを見るには readelf -S
で、
$ gcc -c const.c |
この中で重要なのは、
- セクション[1] .text がプログラムをコンパイルした結果のマシンコード
- [2] .data が読み書き可能データ
- [3] .bss が初期値なしの変数領域
- [8] .symtabがシンボルテーブル
- [9] .strtabが文字列テーブル
- [10] .shstrtabはセクションヘッダ用の文字列テーブル
となる。
シンボルテーブル
シンボルテーブルは readelf -s
で、
$ readelf -s const.o |
オブジェクトファイル内で定義されているグローバルな main
関数が8番に出力されている。
他、2〜7はセクションを指す。
実際のバイナリデータは、セクションヘッダの情報[8]のOffset0x00b0
からSize0x00d8
バイト分格納されていることがわかる。
形式はElf64_Sym構造体(EntSize0x18
バイト)が個数分羅列されている。
文字列テーブル
文字列テーブルはシンボル名を保持するのに使用される。 構造は単に使用される文字列を連結しただけというもので、各文字列はヌルターミネートされている。
セクションテーブルのセクション[9] .strtabはOffsetが0x188でSizeが0x0eとなっているので、そこを見ると:
$ xxd const.o |
シンボル1の const.c
、シンボル8の main
の文字列が格納されている。
空文字列も先頭に配置されている。
シンボルテーブルからは文字列テーブル内の先頭からのオフセット値によって参照される。
例2: 外部参照がある場合
ソース内で定義されないラベルへの外部参照がある場合:
extern int putchar(int); |
再配置テーブルが必要になる。
セクションヘッダ:
$ gcc -c -fno-asynchronous-unwind-tables putchar.c |
再配置テーブル:
$ readelf -r putchar.o # -r で再配置テーブル表示 |
Info
の 4
が Type
の R_X86_64_PLT32
を示していて、
9
は参照するシンボルのインデクスで putchar
:
$ readelf -s putchar.o |
を表している。
Offset
の ..0a
はテキストセクション先頭から書き換え先のオフセット、
Addend
の -4
はアドレス解決時の加算値。
このへん に Type
ごとの再配置の計算式が書いてある。
実際のバイナリデータの形式はElf64_Rela構造体。
例3: データ参照の場合
文字列などのデータがある場合:
const char *get_message() { |
外部参照じゃなくても再配置テーブルが生成される:
$ gcc -c -fno-asynchronous-unwind-tables message.c |
これはプログラム(.text
)とデータ(.rodata
)でセクションが別なので同じオブジェクト内でもオブジェクトファイル生成時にはアドレスを解決させず、リンク時に行えるようにするためだと思う。
Info
の 5
はシンボル番号:
$ readelf -s message.o |
で、ローカルセクション5= .rodata
:
$ readelf -S message.o |
を指している。
オブジェクトファイルでHello world
てなことでオブジェクトファイルを生成するテストとして、Hello, world!を出力するファイルを生成してみる: (注意:x86-64のマシンコードを埋め込んでいるので、x86-64専用)
|
実行する:
$ gcc -o hello_obj hello_obj.c |
出力されたhello.o の中身:
# ELFヘッダ |