読者です 読者をやめる 読者になる 読者になる

yutopp's blog

サンドバッグになりたい

リンカ作ろうJP$1

おはようございます.Aizu Advent Calendar 1日目です!

リンカ作ろう,ということで,リンカを作っている最中にハマったことでもメモっていこうと思います.
間違いがありましたら是非教えて頂けると嬉しいです.

というか普通に間に合いませんでした.申し訳 of the world.
ひとまず,平凡なストレージクラスもつセクションを指すシンボルをリンクすることができます.
要するに,外部のライブラリをimportできません… 本当はputsで文字列を出力させたかったでござる.
現時点でどれ位かというと,この程度のコードをコンパイルしたオブジェクトしかリンクできません.ギョエー

    int hoge()
    {
        char const* const moji = "uhohoho";

        int length = 0;
        for( char const* p = moji; *p != '\0'; ++p ) {
            ++length;
        }

        return length;
    }

    int fuga()
    {
        return hoge();
    }

    int test_entry_point()
    {
        return fuga();
    }

環境は Windows 7(x64),Visual Studio 2012(MSVC v110)です.

リンカ is 何


(調子に乗った図)
コンパイラが生成したオブジェクトファイル(.oだったり.objだったり)は単体では実行できないので,
オブジェクトファイルと必要なライブラリの情報を上手いこと繋ぎあわせて,OSが認識できる形式に整えるものです.

ファイル形式

ライブラリやオブジェクトファイルを繋ぎ合わせるためには個々の情報を引っこ抜く必要があります.
また,そのためにはそれらのファイル形式に対応する必要があります.
今回は,Windowsで色々試していたので,COFF と PE について調べていました.

(仕様)
Microsoft PE and COFF Specification


ライブラリ(アーカイブ) 形式

名前の通り単純な構造で,ヘッダとオブジェクトファイルが連続で詰められている感じです.
一つのファイルが大きいことが多いので,効率良く読み込めるように工夫が必要だと思います.
ライブラリに含まれる関数などのシンボル名を列挙したアーカイブがあるのが素敵です.
個々のヘッダのデリミタが `\n と特徴的(?)なので,エディタでもかなり読みやすいです.
f:id:yutopp:20121130055624p:plain

(参考資料)
ar (UNIX) - Wikipedia
アレ用の何か
Microsoft PE and COFF Specification(6章)


インポートライブラリ 形式

上記の ライブラリ(アーカイブ) 形式 とよく似たフォーマットです.
インポートライブラリヘッダとnullで区切られたインポート名とDLL名が連続で詰められています.
long版 と short版 があります.Windows SDKに含まれる kernel32.lib などはこの形式の short版 のようです.
インポートライブラリヘッダの構造は WinNT.h の IMPORT_OBJECT_HEADER です.
ヘッダは COFF のファイルヘッダにめちゃくちゃ似てます. 気づかないとつらぽよになります.

(参考資料)
Microsoft PE and COFF Specification(7章)


ANON Object

こちらもヘッダが COFF のファイルヘッダにめちゃくちゃ似てる上に,実体もよく分かりません.滅んで欲しい.
バージョンによってヘッダの構造が変わります. WinNT.h では ANON_OBJECT_HEADER と ANON_OBJECT_HEADER_V2 ,ANON_OBJECT_HEADER_BIGOBJ です.
これも COFF のヘッダかと思いこんでひたすら悩みました.つらい.
コンパイラのオプションで生成しないようにできます.とりあえず今回は無視をキメました.

(参考資料)
c - Disassemble Microsoft Visual Studio 2003 compiler output - Stack Overflow


COFF 形式

本命.ライブラリの中に詰められているオブジェクトの形式のひとつです.拡張子は .obj や .o となります.
ファイルヘッダ,セクションヘッダ,セクション,シンボルテーブル,etc なシンプルな構造をしています.
ファイルヘッダ は WinNT.h の IMAGE_FILE_HEADER です.
先頭 2バイトが i386 用のものであることを表す 4C 01 であることが多い感じです.(IMAGE_FILE_HEADER の Machineメンバ が 0x014c)
セクション名には,お馴染みの .text だったり .data などの名前がついています.
.drectve という名前で,かつ IMAGE_SCN_LNK_INFO フラグの立っているセクションには,リンカへの情報が収められているので結構重要です.また,リンカはこの情報を実行形式には含めないようにしたほうが良いみたいです.
シンボルテーブルには,_foo などの関数名と,対応するセクションへの index などが収められています.
0-based の index と 1-based の index が混じっていたりで,注意しないと時間を持っていかれます(体験談)

リンカは,ここからシンボル情報を引っこ抜いて,セクションのデータを展開します.
セクションには再配置情報が含まれているので,そのストレージクラスなどを適切に処理しつつオブジェクトを配置していきます.(デキナカッタ…)

(参考資料)
アレ用の何か
目次(翻訳されたもの.ちょっと古い)


PE 形式

COFFから派生したフォーマットのよう.実行形式です.
DOS用のセクション,PEのヘッダ,セクションヘッダ,セクション…といった構造です.
こちらも PE32 と PE32+ で構造が変わります.PE32+ は 64bit 用に拡張されたものです.
今回はPE32を使いました.WinNT.h の IMAGE_NT_HEADERS と IMAGE_OPTIONAL_HEADER32 あたりがキモです.
重要なものは,IMAGE_OPTIONAL_HEADER32 の SectionAlignment と FileAlignment,加えてNT系だと SizeOfImage の値だと思います.
重要で無さそうな雰囲気を放っていますが,これらの値を正しく設定し,かつ正しく align されていないと実行すら出来ません.(デン!)
なので,吐いた実行形式が正しい形式でないと怒られるときは,一番初めにここを確認すると良いと思います.
SectionAlignment はメモリに展開されるときのアラインメントを指定します.
FileAlignment は実際のファイルに書き込むときのアラインメントです.
SizeOfImage は メモリに展開されるときの大きさを指定します.なので,SectionAlignment の倍数になります.(違うかも?)
後は実際にメモリに展開されるときのことを考えつつ,アドレスを配置していくと大体実行できる気がします.

(参考資料)
EXEファイルの内部構造(PEヘッダ) (1/3):CodeZine


ソースコード

fileformat: yutopp/ytl at develop · GitHub
linker : Dimnal/linker · GitHub
絶賛突貫工事中です.コードが汚いのは分かっています…分かっています…

linkerの動作として,

  1. lib と obj を食わせる
  2. エントリポイントのシンボルから配置していく
  3. 再配置情報に外部シンボル情報が含まれていた場合,読み込み済みの lib と obj から探し出し,配置する
    1. 無い場合 -> リンクエラー
  4. セクションのオフセット分をアドレスに足す
  5. PE 形式に込めて出力

といった感じです.
今はとりあえず動けば良い,な感じで書いています.

そして現在,IMAGE_SYM_CLASS_STATIC というストレージクラスを正しく処理できなくて詰んでます!ギョギョギョ
これが原因で,冒頭のようなコードしかリンクできません.うぅ.

そんなこんなで リンカ作ろうJP は現在進行形です.アドバイス下さい…!


まとめ(?)

"コンパイラを作っている方は多いし,じゃあリンカ書くか" な感じで始まったリンカ作りですが,結構面白いのでオススメです.
とりあえず今回は完成度がアレなのでリベンジしたいところです!ひとまず今回はここまで…


Aizu Advent Calendar 2日目は ___Johniel さんです.宜しくお願いします!
2日目 -> 82連鎖: 台湾に行きました。