アーカイブファイルのフォーマット

2022-06-28
リンカーを自作した際に入力としてELFのオブジェクトファイル(.o)を受け付けるようにしていたが、それだけだとライブラリの扱いで無駄が生じるのでアーカイブファイル(.a)も入力できるようにした。

動機

今までリンク時にライブラリ関数もオブジェクトファイルとしてリンカーに渡していたが、取り扱いが楽なように一つのファイルにまとめていた。 しかしそうすると使われてないライブラリ関数もリンクされてしまうため、生成される実行ファイルのサイズが大きくなってしまう。 関数ごとにオブジェクトファイルに分けて参照されるもののみをリンカーに渡せればよいが、扱いが煩雑になってしまう。

そこでアーカイブファイルを使う。 アーカイブファイルは複数のオブジェクトファイルをまとめたもので、参照された関数やデータだけがリンクされるのでそのような無駄がなくなる。

Linuxのアーカイブファイルのフォーマット

ググって出てくるWikipediaの記事には「arのファイルフォーマットは一度も標準化されたことがない。」と書かれている。 Linuxではたぶん「BSD・GNU共通形式」というものだろう。ファイル先頭から次のような具合になっている:

  • !<arch>\n (8バイト)
  • グローバルヘッダ(struct ar_hdr 、60バイト)

ヘッダの形式は以下の通り(ar.h):

struct ar_hdr {
char ar_name[16]; // ファイル名
char ar_date[12]; // 編集日時
char ar_uid[6], ar_gid[6]; // UID, GID
char ar_mode[8]; // パーミッション
char ar_size[10]; // サイズ
char ar_fmag[2]; // 固定値 (`\n)
};
  • サイズやパーミッション・日時もバイナリではなく、文字列化されている
  • ファイル名が17文字以上にする拡張についてはWikipediaに書かれている(未対応)
  • (グローバルヘッダはリンクにはあまり重要ではない)

で各オブジェクトファイルはどのように格納されているかについては書かれてない…ので実際に ar コマンドで作成したものをダンプして調べた。 見たところオブジェクトファイルの一覧というものはなくて、オブジェクトファイルに含まれる外部公開シンボルの一覧があって、 各シンボルがどのオブジェクトファイルに対応するか、という情報になっている。 グローバルヘッダに続いて次の通り:

  • シンボル数:(4バイト、ビッグエンディアン)
  • 各シンボルが含まれるオブジェクトファイルへのオフセット:(4バイト × シンボル数、ビッグエンディアン)
  • シンボル名の羅列:(\0終端文字列 × シンボル数)

オブジェクトファイルへのオフセットはファイル先頭からのオフセットで、 struct ad_hdr に続いてファイル本体が格納される。

リンカーでの扱い

リンカーにアーカイブファイルを渡した場合には含まれる全てのオブジェクトファイルをリンクするのではなく、参照されたシンボルを含むオブジェクトファイルのみをリンク対象にしてやる。 アーカイブ内のオブジェクトファイルが外部シンボルを参照することもあるので(stdioをunistdのラッパーとして実装するなど)、それも検索対象にする。

コーナーケース

難しいケースは未対応だが、単に与えられたオブジェクトファイル全体でリンクするだけじゃなく色々条件があった気がする:

  • リンカーに与えるファイルとライブラリの順序によって、前のものしか探せなかったりした?ような記憶(要出典)
  • ライブラリに含まれるシンボルと重複しているような場合に、アプリ側はアプリ側の、ライブラリ側はライブラリ側の関数を参照できる、とか

ソース

xcc/src/ld archive.c, archive.h あたり。