自作Cコンパイラではすべて自分で実装してみるというつもりでCコンパイラ、プリプロセッサ、アセンブラを作ってきたが、ついに欠けている最後の要素だったリンカーを作った。
今まではアセンブラですべてのアセンブリコードを受け取って、マシンコード生成とラベルのアドレスを解決して直接ELF形式の実行ファイルを出力していた。 それでほとんど問題はないんだけど、リンカーが使えるとオブジェクトファイルを生成しておくことで分割コンパイルできるようになりコンパイル時間が短縮できたり、 他のコンパイラの出力結果も使用できるようになるという利点がある。
またリンカーがなくてすべてのコードを一緒にアセンブルしていることにより、ソースファイル中の static
変数や関数でも実際には漏れてしまっているため、プログラム全体で衝突しない名前を使う必要があるという欠点があった。
アセンブラからは実行ファイルだけじゃなくオブジェクトファイルの出力もできるようにはしていたので、リンカーを用意できれば一通り揃うことになる。
リンカーの仕事
リンカーは複数のオブジェクトファイルを受け取ってアドレスを計算して実行ファイルを出力するのが主な仕事となる。 各オブジェクトファイルにはシンボルや再配置情報などが含まれているので、それらを組み合わせて処理することになる。
オブジェクトファイルの構造
ELF64フォーマットのオブジェクトファイルの大雑把に解説:
- データがセクションに別れていて、それぞれ実行するマシンコードやデータなどが格納されれている
- シンボルは「このセクションのこのオフセット位置」というのを示していて、外部から参照する場合にどこかわかるようになっている
- 再配置テーブルは「セクション内のこの部分は、シンボルこれこれからこのように計算する」という情報が入っている
もう少し詳しい説明は「ELFのオブジェクトファイル形式を生成する」を見るといいかもしれない。
リンクの処理
実際のリンクの処理の流れは、
- すべてのオブジェクトファイルを読み込む
- セクションに含まれるデータの種類によって分類する
- 各セクションのアドレスを計算する(それによりシンボルのアドレスも決定される)
- 再配置情報でデータを書き換える
- 実行ファイル出力
となる。
以降に重要なデータの構造と扱い方を示す。
セクション
セクションの構造体は以下の通り:
typedef struct { |
sh_type
がセクションタイプSHT_PROGBITS (=1)
がマシンコードやデータを表すsh_flags
のビットで役割を判別:SHF_EXECINSTR (=1<<2)
で実行可能(マシンコード)SHF_WRITE (=1<<0)
で書き込み可
SHT_NOBITS (=8)
だと初期値のない変数(.bss
)領域
シンボル
typedef struct { |
st_info
: 下位4ビットがtype
、上位4ビットがbind
となっているbind
がSTB_LOCAL (=0)
で同じオブジェクトファイル内の別セクション、STB_GLOBAL (=1)
で別オブジェクトファイルのシンボル参照
再配置情報
typedef struct { |
r_offset
: セクション内のオフセット位置r_addend
: 計算結果への加算値r_info
: 下位32ビットがタイプ、上位32ビットがシンボル- 再配置タイプ(主要なもののみ):
R_X86_64_64 (=1)
: 絶対アドレス(64ビット)R_X86_64_PC32 (=2)
:PC
からの相対(32ビット)R_X86_64_PLT32 (=4)
:PC
からの相対(32ビット)
- 再配置タイプ(主要なもののみ):
再配置情報は対応するセクションごとに出力されるので、相対アドレスを計算するための PC
は、再配置のセクションの先頭アドレス + r_offset
となり、実際の値は シンボルのアドレス - PC + r_addend
となる
(STB_LOCAL
(=同オブジェクトファイル別セクション)の場合は セクションの先頭アドレス - PC + r_addend
)。
再配置情報は Elf64_Rela
以外にも Elf64_Rel
という addend
がないものあるようだが自作のアセンブラからは出力されないのでひとまず保留。
再配置タイプも他にいろいろあるが、必要なものだけ実装。
ELFの実行形式
実行形式は以前から出力していたので特に新しいことはないが、「ELF64でQuine」あたりを見るとちょっとわかるかもしれない。
締め
ソースは ld.c 、 今のところソースファイル一つで済むくらいの規模なので、案外単純だった。
静的リンクしかサポートしてないしライブラリファイル.aを扱ってないので今後の課題。
参考
- rui314/mold: mold: A Modern Linker: 低レイヤを知りたい人のためのCコンパイラ作成入門 を書いたRui Ueyama氏の作ったリンカー
- mold での並列処理の説明
- リンカーみたく古くから作られて使われているソフトに今になって革新を起こせるなんてすごいですね
関連リンク
- 未来記事:アーカイブファイルのフォーマット