リンカーは、再配置可能オブジェクトの入力セクションを、作成中の出力ファイル内のセグメントに、自動的にかつ効率的に対応付けします。–M オプションで関連するマップファイル (mapfile) を指定すると、リンカーがデフォルトで行う対応付けを変更することができます。また、mapfile を使用して、新規セグメントの作成、属性の変更、およびシンボルのバージョン情報管理情報の指定を実行できます。
mapfiles のサンプルは、/usr/lib/ld ディレクトリにあります。
セグメント宣言。
対応付け指令。
セクションからセグメントへの順序付け。
サイズシンボル宣言。
ファイル制御指令。
それぞれの指令は複数の行にまたがることができ、最後にセミコロンを付ければ、いくつでも空白 (改行を含む) を入れることができます。
通常、セグメント宣言の後に、対応付け指令を記述します。セグメントを宣言してから、セクションがそのセグメントの一部分になる条件を定義します。対応付け先のセグメントを最初に宣言せずに、対応付け指令あるいはサイズシンボル宣言を入力した場合、組み込みのセグメント以外のセグメントには、デフォルト属性が付与されます。この種のセグメントは、暗示的に宣言されたセグメントになります。
サイズシンボル宣言、およびファイル制御指令は、mapfile のどこにでも入れることができます。
以後のセクションでは、それぞれの指令について説明します。すべての構文説明について、次の表記が適用されます。
固定幅の文字のエントリ、すべてのコロン、セミコロン、等符号、@ 記号は、そのままの文字を入力する。
「斜体文字」で示されたエントリはすべて、適切なもので置き換える。
{ .... }* は「なしまたはそれ以上」を意味する。
{ .... }+ は「1 つまたはそれ以上」を意味する。
[ .... ] は「オプション」を意味する。
section_names と segment_names は、C 識別子と同じ規則に従い、ピリオド (.) は文字として処理される。たとえば、.bss は正当な名前です。
section_names、segment_names、file_names、および symbol_names には大文字と小文字の区別がある。それ以外のものには大文字と小文字の区別はない。
空白文字 (あるいは改行文字) は、数字の前および名前や値の間以外はどこにでも入れられる。
# で始まり改行で終わるコメントは、空白文字を入れることができる場所であれば、どこにでも入れられる。
セグメントの宣言により、出力ファイルに新しいセグメントを作成したり、既存のセグメントの属性値を変更したりできます。既存のセグメントとは、以前に定義したもの、あるいは次に述べる 4 つの組み込みセグメントの 1 つのことです。
セグメントの宣言は次の構文で行います。
segment_name = {segment_attribute_value}*;
各 segment_name に対して、任意の数の segment_attribute_values を任意の順序で指定し、それぞれは空白文字で区切ります。セグメント属性ごとに 1 つの値だけを指定できます。セグメント属性および有効な値を、次の表に示します。
|
4 つの組み込みセグメントが存在し、次のデフォルト属性値を保持します。
text – LOAD、?RX、virtual_address と physical_address と length は指定なし。alignment 値は CPU タイプごとにデフォルトに設定。
data – LOAD、?RWX、virtual_address と physical_address と length は指定なし。alignment 値は CPU タイプごとにデフォルトに設定。
bss – 無効、LOAD、?RWX、virtual_address と physical_address と length は指定なし。alignment 値は CPU タイプごとにデフォルトに設定。
note – NOTE。
デフォルトでは、bss セグメントは無効に設定されています。この唯一の入力部分である SHT_NOBITS タイプのセクションは、データセグメント内でキャプチャーされます。Table 12–5 セクションの詳細は、Table 12–5 を参照してください。bss セグメントの作成を有効にするときは、もっとも単純な bss 宣言だけでかまいません。
bss =;
すべての SHT_NOBITS セクションは、データセグメントではなく、このセグメントによりキャプチャーされるようになります。もっとも単純な場合、ほかのセグメントにも適用されるデフォルトを使用してこのセグメントの整列が行われます。宣言を実行してセグメントの追加属性を指定することにより、セグメント作成を有効にしたり、指定した属性を付与したりすることもできます。
リンカーは、mapfile を読み取る前に、これらのセグメントが宣言されたように動作します。mapfile オプションのデフォルトを参照してください。
セグメント宣言を入力する場合、次のことに注意してください。
数字には、C 言語と同じ形式で、16 進数、10 進数、あるいは 8 進数が使えます。
V、P、L、R、あるいは A と数字の間には空白文字を入れてはいけません。
segment_type 値は、LOAD、NOTE、NULL、または STACK のいずれかです。未指定の場合、セグメントタイプはデフォルトの LOAD に設定されます。
segment_flags 値は、R は読み取り可能 、W は書き込み可能、X は実行可能、O は順番を表します。疑問符 ? と segment_flags 値を構成する個々のフラグの間には、空白文字を入れてはいけません。
LOAD セグメントの segment_flags 値は、デフォルトで RWX になります。
NOTE セグメントには、segment_type 以外のセグメント属性値は割り当てられません。
STACK 値の segment_type が 1 つ許可されます。segment_flags から選択されたセグメントのアクセス要求だけを指定できます。
暗示的に宣言されたセグメントでは、segment_type 値は LOAD、segment_flags 値は RWX、 virtual_address と physical_address と整列値はデフォルト、そして長さは無制限になります。
LOAD セグメントには、virtual_address 値または physical_address 値、および最大セグメント length 値を明示的に指定できます。
セグメントに ? の segment_flags 値があって後に何もない場合、値は読み取り不可、書き込み不可、および実行不可になります。
alignment 値は、セグメントの最初の仮想アドレスを計算する際に使われます。この整列は、整列の指定されたセグメントにだけ影響します。その他のセグメントは、その alignment 値が変更されないかぎり、デフォルトの整列が使われます。
属性値 virtual_address、physical_address、length のいずれかが設定されていない場合、リンカーは出力ファイルの作成時にこれらの値を計算します。
セグメントに対して alignment 値が指定されていない場合、整列が組み込みのデフォルトに設定されます。デフォルトは CPU により異なり、ソフトウェアのリビジョンによっても異なる場合があります。
virtual_address と alignment 値の両方がセグメントに対して指定されている場合、virtual_address の方が優先されます。
virtual_address 値がセグメントに対して指定されている場合、プログラムヘッダーの整列フィールドには、デフォルトの整列値が設定されます。
rounding 値がセグメントに対して設定されている場合、そのセグメントの仮想アドレスは与えられた値に一致する次のアドレスに丸められます。この値は、値の指定対象のセグメントにしか効力はありません。値が入力されないと、丸めは行われません。
?E フラグにより、空のセグメントが作れます。この空のセグメントには、関連付けられたセクションが存在しません。このセグメントは LOAD セグメントまたは NULL セグメントにできます。空の LOAD セグメントは、実行可能ファイルに対してのみ指定できます。これらのセグメントは、指定されたサイズと整列を保持する必要があります。これらのセグメントにより、プロセスの起動時にメモリー予約が作成されます。空の NULL セグメントは、事後処理ユーティリティーで使用できるプログラムヘッダーエントリを追加するためのものです。これらのセグメントに追加属性を指定すべきではありません。LOAD セグメントと NULL セグメントは複数定義してもかまいません。
?N フラグにより、ELF ヘッダー、および任意のプログラムヘッダーを最初のロード可能なセグメントの一部分として含めるかどうかを制御できます。デフォルトでは、 ELF ヘッダーおよびプログラムヘッダーは、最初のセグメントに含まれます。これらのヘッダー内の情報は、対応付けられたイメージ内で (通常は実行時リンカーにより) 使用されます。?N オプションを使用すると、イメージの仮想アドレス計算が最初のセグメントの最初のセクションで開始されます。
?O フラグを使用すると、出力ファイル内のセクションの順序を制御できます。このフラグは、コンパイラの –xF オプションと合わせて使うようになっています。ファイルを –xF オプションを使ってコンパイルすると、そのファイル内の各関数が、.text セクションと同じ属性を持つ別個のセクションに置かれます。これらのセクションは、.text%function_name (function_name は関数名) という名前です。
たとえば、main()、foo()、および bar() の 3 つの関数を持つファイルを –xF オプションを使ってコンパイルすると、再配置可能オブジェクトファイルが作成され、3 つの関数のテキストが .text%main、.text%foo、および .text%bar という名前のセクションに配置されます。–xF オプションは 1 つのセクションに 1 つの関数を割り当てるので、セクションの順番を制御するために ?O フラグを使うと、実際には関数の順番を制御することになります。
次のユーザー定義の mapfile を検討します。
text = LOAD ?RXO; text: .text%foo; text: .text%bar; text: .text%main;
最初の宣言により、?O フラグがデフォルトのテキストセグメントに関連付けられます。
ソースファイルの関数定義の順序が、main、foo、および bar の場合、最終的な実行プログラムには foo、bar、および main の順序で関数が含まれます。
同じ名前の静的関数を対象とする場合、ファイル名も指定する必要があります。?O フラグを指定すると、mapfile で指定されたとおりにセクションの順序付けが行われます。たとえば、静的関数 bar() がファイル a.o および b.o にあって、ファイル a.o() の関数 bar を、ファイル b.o() の関数 bar の前に置く場合、mapfile のエントリは次のようになります。
text: .text%bar: a.o; text: .text%bar: b.o;
次の構文を入力することもできます。
text: .text%bar: a.o b.o;
しかし、ファイル a.o の関数 bar() がファイル b.o の関数 bar() の前に置かれることは保証されません。2 番目の形式は、結果が予測できないため、お勧めしません。
対応付け指令は、入力セクションを出力セグメントに対応付けする方法をリンカーに伝えます。基本的には、対応付け先のセグメントの名前を指定し、名前を指定したセグメントに対応付けするためにセクションの属性をどうすべきかを指定します。特定のセグメントに対応付けするためにセクションが持っていなければならないセクション属性値 (section_attribute_values) のセットは、そのセグメントのエントランス基準と呼ばれます。出力ファイル内の指定されたセグメントにセクションを置くには、セクションがセグメントのエントランス基準に正確に合致している必要があります。
対応付け指令には次のような構文があります。
segment_name : {section_attribute_value}* [: {file_name}+];
segment_name に対して、任意の数の section_attribute_values を任意の順序で指定し、それぞれは空白文字で区切ります。セクション属性ごとに 1 つの値だけを指定できます。file_name 宣言を使用して、特定の .o ファイルに由来するセクションだけに限定することも可能です。セクション属性とその有効値は次の表に示すとおりです。
|
対応付け指令を入力する場合、次の点に注意してください。
前記の section_types から 1 つ以下の section_type を選択する必要があります。前記の section_types は組み込まれています。section_types の詳細については、セクションを参照してください。
section_flags 値は、A は割り当て可能 、W は書き込み可能、X は実行可能です。個々のフラグの前に感嘆符 (!) がついている場合、リンカーは、フラグが設定されていないことを確認します。section_flags 値を構成する疑問符、感嘆符、および個々のフラグの間には空白文字を入れてはいけません。
file_name には、ファイル名として正当な名前を *filename または archive_name(component_name) の形式で指定できます (例、/lib/libc.a(printf.o))。リンカーは、ファイル名の構文をチェックしません。
file_name が *filename の形式になっている場合、リンカーはコマンド行からファイルの basename(1) を判断します。このベース名を使って、指定された file name との一致が実行されます。言い換えれば、mapfile で指定する filename は、コマンド行で指定されたファイル名の最後の部分だけが合致する必要があります。対応付けの例を参照してください。
リンク編集で –l オプションを使用するときに、–l オプションのあとのライブラリがカレントディレクトリ内にある場合は、そのライブラリを検出させるため、名前の前に ./ を付けるか、またはパス名全体を mapfile 内で記述する必要があります。
特定の出力セグメントについて複数の指令行を指定できます。たとえば、次に示す一連の指令を行うことができます。
S1 : $PROGBITS; S1 : $NOBITS;
1 つのセグメントに対して複数の対応付け指令行を指示することは、複数のセクション属性値を指定するための唯一の方法です。
1 つのセクションは複数のエントランス基準に合致することがあります。その場合、mapfile で最初にエントランス基準が合致したセグメントが使われます。たとえば、mapfile が次のようになっているとします。
S1 : $PROGBITS; S2 : $PROGBITS;
この場合、$PROGBITS セクションは、セグメント S1 に対応付けられます。
次のような表記を使うことにより、セグメント内にセクションを配置する順序を指定できます。
segment_name | section_name1; segment_name | section_name2; segment_name | section_name3;
上記の形式で指定されたセクションは、すべての名前なしセクションの前に、mapfile に列挙されている順序で配置されます。
サイズシンボル宣言を使って、指定したセグメントのサイズをバイトで示す大域絶対シンボルを定義できます。このシンボルは、オブジェクトファイル内で参照できます。サイズシンボルは次の構文で宣言します。
segment_name @ symbol_name;
symbol_name には、任意の正当な C 識別子を指定できます。リンカーは、symbol_name の構文をチェックしません。
ファイル制御指令により、共有オブジェクト内のどのバージョンの定義をリンク編集時に使用可能にするかを指定できます。ファイル制御構文で宣言します。
shared_object_name - version_name [ version_name .... ];
version_name (バージョン名) には、指定した shared_object_name (共有オブジェクト名) 内のバージョン定義名が入ります。
次にユーザー定義の mapfile の例を示します。左の数字は、説明のためにつけたものです。実際には、数字の右の情報だけが、mapfile に含まれます。
使用例 B-1 ユーザー定義の mapfile1. elephant : .data : peanuts.o *popcorn.o; 2. monkey : $PROGBITS ?AX; 3. monkey : .data; 4. monkey = LOAD V0x80000000 L0x4000; 5. donkey : .data; 6. donkey = ?RX A0x1000; 7. text = V0x80008000;
4 つの別々のセグメントがこの例では扱われています。暗黙の内に宣言されたセグメント elephant (1 行目) は、ファイル peanuts.o および popcorn.o からすべての .data セクションを受け取ります。*popcorn.o は、リンカーに与えられるすべての popcorn.o ファイルと一致します。ファイルは、現在のディレクトリにある必要はありません。他方、/var/tmp/peanuts.o がリンカーに提供された場合、これには * が前に付かないため peanuts.o とは一致しません。
暗黙の内に宣言されたセグメント monkey (2 行目) は $PROGBITS および割り当て可能な実行プログラム (?AX) の両方になっているすべてのセクション、さらに .data (3 行目) という名前のすべてのセクション (セグメント elephant に入らなかったもの) を受け取ります。別の行で section_name に section_type と section_flags の値が指定されているので、monkey セグメントに入る .data セクションが、$PROGBITS あるいは割り当て可能な実行プログラムである必要はありません。
同じ行の属性の間には「and」関係があります。たとえば、2 行目の $PROGBITS と ?AX は「and」関係です。同じセグメントの属性が複数行にある場合は、それらの間に「or」関係があります。たとえば、2 行目の $PROGBITS ?AX と 3行目の .data は「or」関係です。
monkey セグメントは、segment_type 値が LOAD、segment_flags 値が RWX、virtual_address、physical_address、length、および alignment の値は無指定 (デフォルトが使用) として、2 行目で暗黙の内に宣言されています。4 行目では、monkey の segment_type 値は LOAD に設定されています。segment_type 属性は変わらないため、警告は出ません。virtual_address 値は 0x80000000 に、最大 length 値は 0x4000 に設定されています。
5 行目では、donkey セグメントを暗黙の内に宣言しています。エントランス基準 は、このすべての .data セクションをこのセグメントに振り向けるようになっています。実際には、3 行目の monkey のエントランス基準はこれらのセクションのすべてを取り込むので、このセグメントに入るセクションはありません。6 行目では、segment_flags 値は ?RX に、alignment 値は 0x1000 に設定されています。これらの属性値の両方が変化するため、警告が出ます。
7 行目では、テキストセグメントの virtual_address 値を 0x80008000 に設定します。
ユーザー定義の mapfile の例は、説明のために警告を出すように設計されています。次の例では指令の順序を変更して警告を避けています。
1. elephant : .data : peanuts.o *popcorn.o; 4. monkey = LOAD V0x80000000 L0x4000; 2. monkey : $PROGBITS ?AX; 3. monkey : .data; 6. donkey = ?RX A0x1000; 5. donkey : .data; 7. text = V0x80008000;
次の mapfile の例では、セグメント内セクションの順序付けを使っています。
1. text = LOAD ?RXN V0xf0004000; 2. text | .text; 3. text | .rodata; 4. text : $PROGBITS ?A!W; 5. data = LOAD ?RWX R0x1000;
text セグメントおよび data セグメントが、この例では扱われています。text セグメントの virtual_address が 0xf0004000 になるように、またこのセグメントのアドレス計算の一部分として ELF ヘッダーやプログラムヘッダーを含めないように宣言しています。2 行目と 3 行目では、セグメント内セクションの順序付けを有効にし、このセグメントでは .text セクションと .rodata セクションが最初の 2 つのセクションになるように指定しています。この結果、.text セクションは仮想アドレスが 0xf0004000 になり、その直後に .rodata セクションがきます。
text セグメントを構成するその他の $PROGBITS セクションは、.rodata セクションのあとにきます。5 行目では data セグメントを指定し、その仮想アドレスは 0x1000 バイト境界で始まらなければならないと指定しています。data を構成する最初のセクションも、ファイルイメージ内の 0x1000 バイト境界に存在します。
リンカーは、デフォルトの segment_attribute_values を持つ 4 つの組み込みセグメント (text、data、bss、note)、および対応するデフォルトの対応付け指令を定義します。リンカーはデフォルトを提供するのに実際の mapfile は使いませんが、ここではデフォルトの内容を mapfile に書かれているものとして、リンカーがユーザーの mapfile を処理する際にどのようなことが起こるかを説明します。
次に示す例は、リンカーのデフォルトを mapfile として表現したものです。リンカーは、mapfile をすでに読み取ったかのように実行を開始します。その後でリンカーは、mapfile を読み取ったり、あるいはデフォルトへの追加や変更を行なったりします。
text = LOAD ?RX; text : ?A!W; data = LOAD ?RWX; data : ?AW; note = NOTE; note : $NOTE;
mapfile の各セグメント宣言は読み取られる際、次のようにセグメント宣言の既存リストと比較されます。
セグメントが mapfile に存在しておらず、同じ segment-type 値の別のセグメントが存在する場合、そのセグメントは、すべての同じ segment_type の既存セグメントの前に追加されます。
セグメントが読み取られたとき、既存の mapfile に同じ segment_type のセグメントがない場合、セグメントは、segment_type 値が次の順序になるように追加されます。
INTERP
LOAD
DYNAMIC
NOTE
セグメントの segment_type が LOAD で、この LOAD 可能なセグメントに virtual_address 値を定義していた場合、セグメントは、virtual_address 値が定義されていない、あるいはより高い virtual_address 値の LOAD が指定されたセグメントの前、そしてそれよりも低い virtual_address 値のセグメントの後に置かれます。
mapfile の各対応付け指令が読み取られる際、同じセグメントに対してすでに指定されたその他の対応付け指令の後 (かつ、そのセグメントのデフォルトの対応付け指令の前) に、指令が追加されます。
ELF ベースのリンカーのもっとも重要なデータ構造の 1 つとして対応付け構造があります。モデルデフォルト mapfile に対応するデフォルト対応付け構造が、リンカーにより使用されます。ユーザーの mapfile はすべて、デフォルト対応付け構造内に特定の値を追加またはオーバーライドします。
一般的な (ただし多少簡略化した) 対応付け構造をFigure B–1 に示します。「エントランス基準」ボックスは、デフォルトの対応付け指令内の情報に対応しています。「セグメント属性記述子」ボックスは、デフォルトのセグメント宣言内の情報に対応しています。「出力記述子」ボックスは、各セグメントの下に来るセクションの詳細な属性を示します。セクション自体は丸で囲んであります。
図 B-1 簡単な対応付け構造
リンカーはセクションをセグメントに対応付ける際に、次の手順で行います。
セクションを読み取る際に、リンカーは合致するものを見つけるために、一連のエントランス基準を検査します。指定したすべての条件が合致する必要があります。
Figure B–1 では、text セグメントに入るセクションの場合、section_type は $PROGBITS、section_flags は ?A!W でなければなりません。エントランス基準では名前は指定されていないので、名前 .text は必要ありません。エントランス基準では実行ビットは何も指定されていないので、セクションは section_flags 値の X または !X のどちらかでもかまいません。
エントランス基準に合致するものが見つからない場合、セクションはその他すべてのセグメントの後の出力ファイルの最後に置かれます。この情報に関するプログラムヘッダーエントリは作成されません。
セクションがセグメントの中に入る際に、リンカーは次のようにそのセグメントの既存の一連の出力セクション記述子を検査します。
セクションの属性値が既存の出力セクション記述子の属性値とまったく合致する場合、セクションは出力セクション記述子に対応するセクションの列挙の最後に置かれます。
たとえば、section_name が .data1、section_type が $PROGBITS、section_flags が ?AWX のセクションは、Figure B–1 の 2 番目のエントランス基準ボックスにあてはまり、data セグメントに置かれます。セクションは 2 番目の出力セクション記述子ボックスにちょうど一致し (.data1、$PROGBITS、?AWX)、このボックスに対応する一連のセクションの最後に追加されます。fido.o、rover.o、および sam.o の .data1 セクションは、このことを示しています。
一致する出力セクション記述子が見つからず、同じ section_type の出力セクション記述子がほかに存在する場合、セクションとして同じ属性値で新しい出力セクション記述子が作られ、そのセクションは新しい出力セクション記述子と対応づけられます。出力セクション記述子およびセクションは、同じセクションタイプの最後の出力セクション記述子の後に置かれます。Figure B–1 の Figure B–1 セクションはこの方法で配置されました。
指定されたセクションタイプの出力セクション記述子がほかに存在しない場合、新しい出力セクション記述子が作られ、セクションはそのセクション内に置かれます。
すべてのコマンド行で指定されたオブジェクトファイルとライブラリが読み取られた後、セグメントがセクションをまったく含まない場合、そのセグメントに対してはプログラムヘッダーエントリは作られません。