CSAPP 第7章 リンク
コンパイルの流れ
- ソースコードをプリプロセッサにより改変されたソースコードにする
- コンパイラによりアセンブリに変換する
- アセンブラにより、リロケータブルオブジェクト・プログラムに変換する
- リンカにより、実行可能オブジェクトファイルに変更する
リンクとは
コードやデータを集めて、一つのファイルにまとめあげ、メモリ上にロード(コピー)して実行できるようにする過程である
プリプロセッサ
コンパイルするソースコードに含まれている#
の部分を解析して、コードを出力する
-
# define
指定されているマクロを値として持ってくる -
# ifndef
ifndefがtrueならば定義をコードに反映 -
# include
ローカルに保存されているコードを持ってくる
プリプロセッサのdefineの例
#include "main.h"
int main()
{
int r = 2;
int result = PI * r * r;
return (0);
}
#ifndef MAIN_H
# define MAIN_H
# define PI 3.14
#endif
# 1 "main.c"
# 1 "<built-in>" 1
# 1 "<built-in>" 3
# 368 "<built-in>" 3
# 1 "<command line>" 1
# 1 "<built-in>" 2
# 1 "main.c" 2
# 1 "./main.h" 1
# 2 "main.c" 2
int main()
{
int r = 2;
int result = 3.14 * r * r;
return (0);
}
.iファイルにおけるbuilt-in, command-lineにおける解説
# 1 "main.c"
から# 1 "main.c"
にかけて出力された行はlinemarker
と呼びincludeするファイルの呼び出しを行っている。
# linenum filename flags
このように表され、linenumが行数。flagsが各操作を表すことになる。
flagsは1 ~ 4まであり、それぞれ
- 新しいファイルの最初を示す
- ファイルを返すことを示す
- システムのヘッダーファイルからlinenumを読み取って返すことを示す
- 暗黙的なextern "C" ブロックがあることを示す
詳しいことはよくわからないが、1でopen。3でもともとあるライブラリをlinenum分だけinclude。
2で閉じる。的なことをやっているような気がする。
コンパイラ
.iファイルを.sファイルに変換
アセンブラ
.sファイルを再配置可能オブジェクトファイル(リロケータブル)に変換する
リンカ
リロケータブルオブジェクトファイルを統合して、実行可能オブジェクトファイルに変換する
この辺は後々詳しくやるため今は省略
ローダ
シェルがOSの関数を呼び出し、実行可能ファイルのコードとデータをメモリにコピーして、プログラムの先頭に制御を移す
静的リンク
リロケータブルオブジェクトファイルなどを実行可能ファイルに固めること
静的にすることで、実行可能ファイルは変更不可となる
リンカの作業
- シンボル解決
関数、グローバル変数、静的変数を一意に定めるということ - 再配置
オブジェクトファイル全体として適切な配置関係になるように、メモリ位置を再配置する
オブジェクトファイル
- 再配置可能オブジェクトファイル
コンパイル時に他の再配置可能なオブジェクトファイルと統合して実行可能オブジェクトファイルを生成できる - 実行可能オブジェクトファイル
実行可能なバイナリコード、データを含む - 共有オブジェクトファイル
特殊な再配置可能なオブジェクトファイル
ELF
実行可能かつリンク可能な形式
.exeファイルのようなもの
こちらの記事が良さげ
ELFヘッダ
様々なメタデータを含んでいる
.data
静的変数
.symtab
関数に関する情報を集めたシンボルテーブル
.bss
未初期化データ プログラム内では扱えないデータ
実行ファイル内で無駄なリソースを使わないためにわざわざ別にして扱う
ソースファイルはASCIIコードで書かれている。
これを分析して、実行可能オブジェクトファイルへと変換する
strong symbol , weak symbol
同名のシンボルが存在する時、そのシンボルをstrongとweakに分類する。
そして以下の規則に従って、リンクを行う。
- 同名のstrongシンボルがあったらエラー
- 同名のstrongシンボルとweakシンボルがあった場合、strongが優先される
- 同名のweakシンボルがあった場合、どちらかを選ぶ
weakの競合は意図しないバグを引き起こすので、-Werrorで回避するとよい。
アーカイブファイルとリンクさせるときは、a→bの依存があったら
gcc a b
と記載しないとリンカーエラーになる。これはリンカのアルゴリズムによるものである
再配置
シンボル解決が終わるとコード内のシンボルが一意に定まる。
これにより、実際に実行にロードするアドレスを割り当てる作業が始まる。
これを再配置という。
- セクション及びシンボル定義を再配置する
入力モジュールの全ての.dataセクションを統合して、実行可能オブジェクトにおける唯一の.dataセクションとする。各シンボルにもメモリアドレスを割り当てる - セクション内のシンボル参照を再配置する。
コード及びデータセクションで、シンボルを参照するアドレスが正しいアドレスを指すようにする。
再配置エントリ
リンカがオブジェクトファイルを統合して、実行可能ファイルを作るときに参照の修正を行うが、
それをどのように修正するかは再配置エントリ
によって行われる。
コードの再配置エントリは.rel.text
データの再配置エントリは.rel.data
上記セクションによって保存を行う
実行可能オブジェクトファイルのロード
linuxにおいてはexecve関数を使ってオブジェクトファイルをロードする
まずプログラムヘッダテーブルに記載されている、コード/データセグメントにコピーする。
次にプログラムのエントリポイントにジャンプする。