私はいつも疑問に思っていました。コンパイラは、あなたが書いたコードをバイナリに変換することを知っていますが、リンカは何をしますか?彼らはいつも私にとって謎でした。
「リンク」とは大まかに理解しています。ライブラリとフレームワークへの参照がバイナリに追加されたときです。それ以上はわかりません。私にとってはそれは「うまくいく」だけです。動的リンクの基本も理解していますが、あまり深くありません。
誰かが用語を説明できますか?
私はいつも疑問に思っていました。コンパイラは、あなたが書いたコードをバイナリに変換することを知っていますが、リンカは何をしますか?彼らはいつも私にとって謎でした。
「リンク」とは大まかに理解しています。ライブラリとフレームワークへの参照がバイナリに追加されたときです。それ以上はわかりません。私にとってはそれは「うまくいく」だけです。動的リンクの基本も理解していますが、あまり深くありません。
誰かが用語を説明できますか?
回答:
リンカーを理解するには、ソースファイル(CまたはC ++ファイルなど)を実行可能ファイル(実行可能ファイルとは、マシンで実行できるファイルまたは同じマシンアーキテクチャを実行している他の誰かのマシン)。
内部的には、プログラムがコンパイルされると、コンパイラはソースファイルをオブジェクトバイトコードに変換します。このバイトコード(オブジェクトコードと呼ばれることもあります)は、コンピュータアーキテクチャのみが理解できるニーモニック命令です。従来、これらのファイルの拡張子は.OBJです。
オブジェクトファイルが作成された後、リンカが機能します。多くの場合、有用なことを行う実際のプログラムでは、他のファイルを参照する必要があります。たとえば、Cでは、名前を画面に出力する簡単なプログラムは次のように構成されます。
printf("Hello Kristina!\n");
コンパイラがプログラムをobjファイルにコンパイルすると、printf
関数への参照が配置されます。リンカはこの参照を解決します。ほとんどのプログラミング言語には、その言語から期待される基本的なものをカバーするルーチンの標準ライブラリがあります。リンカは、OBJファイルをこの標準ライブラリにリンクします。リンカは、OBJファイルを他のOBJファイルとリンクすることもできます。別のOBJファイルから呼び出すことができる関数を持つ他のOBJファイルを作成できます。リンカは、ワードプロセッサのコピーアンドペーストのように機能します。プログラムが参照する必要なすべての機能を「コピー」して、単一の実行可能ファイルを作成します。コピーされる他のライブラリは、さらに他のOBJまたはライブラリファイルに依存している場合があります。時々、リンカはその仕事をするためにかなり再帰的にならなければなりません。
すべてのオペレーティングシステムが単一の実行可能ファイルを作成するわけではないことに注意してください。たとえば、WindowsはDLLを使用して、これらすべての機能を1つのファイルにまとめています。これにより、実行可能ファイルのサイズは小さくなりますが、実行可能ファイルはこれらの特定のDLLに依存します。DOSは、オーバーレイ(.OVLファイル)と呼ばれるものを使用していました。これには多くの目的がありましたが、1つは一般的に使用される関数を1つのファイルにまとめることでした(不思議なことに、別の目的は、大きなプログラムをメモリに収めることができるようにすることでした。DOSのメモリとオーバーレイには制限があります。メモリから「アンロード」され、他のオーバーレイがそのメモリの上に「ロード」される可能性があるため、「オーバーレイ」という名前です。Linuxは共有ライブラリを持っています。これは基本的にDLLと同じ考えです(私が知っているハードコアLinuxの連中は多くの大きな違いがあると私に告げるでしょう)。
これがあなたの理解に役立つことを願っています!
アドレスの再配置は、リンクの重要な機能の1つです。
それでは、最小限の例でそれがどのように機能するかを見てみましょう。
概要:再配置は、.text
翻訳するオブジェクトファイルのセクションを編集します。
コンパイラは一度に1つの入力ファイルしか認識しないため、これはリンカで行う必要がありますが、次の方法を決定するには、すべてのオブジェクトファイルについて一度に知る必要があります。
.text
と.data
セクションを衝突させない前提条件:以下についての最小限の理解:
リンクは特にCまたはC ++とは関係ありません。コンパイラはオブジェクトファイルを生成するだけです。次に、リンカはそれらをコンパイルした言語を知ることなく、それらを入力として受け取ります。Fortranも同様です。
そこで、クラストを減らすために、NASM x86-64 ELF Linux hello worldを調べてみましょう。
section .data
hello_world db "Hello world!", 10
section .text
global _start
_start:
; sys_write
mov rax, 1
mov rdi, 1
mov rsi, hello_world
mov rdx, 13
syscall
; sys_exit
mov rax, 60
mov rdi, 0
syscall
コンパイルおよびアセンブル:
nasm -o hello_world.o hello_world.asm
ld -o hello_world.out hello_world.o
NASM 2.10.09。
まず.text
、オブジェクトファイルのセクションを逆コンパイルします。
objdump -d hello_world.o
それは与える:
0000000000000000 <_start>:
0: b8 01 00 00 00 mov $0x1,%eax
5: bf 01 00 00 00 mov $0x1,%edi
a: 48 be 00 00 00 00 00 movabs $0x0,%rsi
11: 00 00 00
14: ba 0d 00 00 00 mov $0xd,%edx
19: 0f 05 syscall
1b: b8 3c 00 00 00 mov $0x3c,%eax
20: bf 00 00 00 00 mov $0x0,%edi
25: 0f 05 syscall
重要な行は次のとおりです。
a: 48 be 00 00 00 00 00 movabs $0x0,%rsi
11: 00 00 00
これは、hello world文字列のアドレスをrsi
レジスタに移動する必要があります。これは、書き込みシステムコールに渡されます。
ちょっと待って!コンパイラー"Hello world!"
は、プログラムがロードされたときにメモリー内のどこに配置されるかをどのようにして知ることができますか?
特に.o
、多数のファイルを複数の.data
セクションとリンクした後はできません。
リンカだけがそれを行うことができます。彼だけがそれらのオブジェクトファイルをすべて持つことになるからです。
だからコンパイラはただ:
0x0
コンパイルされた出力にプレースホルダー値を置きますこの「追加情報」は.rela.text
、オブジェクトファイルのセクションに含まれています
.rela.text
「.textセクションの再配置」を表します。
リンカはオブジェクトから実行可能ファイルにアドレスを再配置する必要があるため、ワード再配置が使用されます。
.rela.text
セクションは次のように分解できます。
readelf -r hello_world.o
を含む;
Relocation section '.rela.text' at offset 0x340 contains 1 entries:
Offset Info Type Sym. Value Sym. Name + Addend
00000000000c 000200000001 R_X86_64_64 0000000000000000 .data + 0
このセクションの形式は、次の場所に文書化されています:http : //www.sco.com/developers/gabi/2003-12-17/ch4.reloc.html
各エントリは、再配置する必要がある1つのアドレスについてリンカに通知します。ここでは、文字列に対して1つしかありません。
少し単純化すると、この特定の行について、次の情報が得られます。
Offset = C
:.text
このエントリが変更するの最初のバイトは何ですか。
逆コンパイルされたテキストを振り返ると、それはまさにcriticalの内部にあり、movabs $0x0,%rsi
x86-64命令エンコーディングを知っている人は、これが命令の64ビットアドレス部分をエンコードしていることに気付くでしょう。
Name = .data
:アドレスは.data
セクションを指します
Type = R_X86_64_64
、住所を変換するために実行する必要がある計算を正確に指定します。
このフィールドは実際にはプロセッサに依存しているため、AMD64 System V ABI拡張セクション4.4「再配置」に記載されています。
その文書はそれR_X86_64_64
が行うと言います:
Field = word64
:8バイト、つまり00 00 00 00 00 00 00 00
atアドレス0xC
Calculation = S + A
S
は、再配置されるアドレスの値なので、00 00 00 00 00 00 00 00
A
0
ここにある加数です。これは、再配置エントリのフィールドです。したがってS + A == 0
、.data
セクションの最初のアドレスに移動します。
次に、ld
生成された実行可能ファイルのテキスト領域を見てみましょう。
objdump -d hello_world.out
与える:
00000000004000b0 <_start>:
4000b0: b8 01 00 00 00 mov $0x1,%eax
4000b5: bf 01 00 00 00 mov $0x1,%edi
4000ba: 48 be d8 00 60 00 00 movabs $0x6000d8,%rsi
4000c1: 00 00 00
4000c4: ba 0d 00 00 00 mov $0xd,%edx
4000c9: 0f 05 syscall
4000cb: b8 3c 00 00 00 mov $0x3c,%eax
4000d0: bf 00 00 00 00 mov $0x0,%edi
4000d5: 0f 05 syscall
したがって、オブジェクトファイルから変更されたのは重要な行のみです。
4000ba: 48 be d8 00 60 00 00 movabs $0x6000d8,%rsi
4000c1: 00 00 00
これはの代わりに0x6000d8
(d8 00 60 00 00 00 00 00
リトルエンディアンの)アドレスを指すようになりました0x0
。
これはhello_world
文字列の正しい場所ですか?
決定するには、各セクションをどこにロードするかをLinuxに指示するプログラムヘッダーをチェックする必要があります。
以下を使用して分解します。
readelf -l hello_world.out
それは与える:
Program Headers:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000
0x00000000000000d7 0x00000000000000d7 R E 200000
LOAD 0x00000000000000d8 0x00000000006000d8 0x00000000006000d8
0x000000000000000d 0x000000000000000d RW 200000
Section to Segment mapping:
Segment Sections...
00 .text
01 .data
これ.data
は、2番目のセクションがVirtAddr
= で始まることを示してい0x06000d8
ます。
そして、データセクションの唯一のものは、hello world文字列です。
「C」のような言語では、コードの個々のモジュールは伝統的にオブジェクトコードのblobに個別にコンパイルされます。これは、モジュールがそれ自体の外部(つまり、ライブラリまたは他のモジュール)から行うすべての参照以外のあらゆる点で実行する準備ができています。まだ解決されていません(つまり、何も表示されず、すべての接続が確立されるまで保留されます)。
リンカが行うことは、すべてのモジュールをまとめて確認し、各モジュールが外部に接続するために必要なものを確認し、エクスポートするすべてのものを確認することです。次に、それをすべて修正し、最終的な実行可能ファイルを生成して、実行できるようにします。
動的リンクも行われている場合、リンカーの出力はまだ実行できません-まだ解決されていない外部ライブラリへの参照がまだあり、アプリをロードするときにOSによって解決されます(または後で実行中にも)。
コンパイラがオブジェクトファイルを生成するとき、そのオブジェクトファイルで定義されているシンボルのエントリと、そのオブジェクトファイルで定義されていないシンボルへの参照が含まれます。リンカはそれらを受け取り、それらをまとめて(すべてが正しく機能する場合)、各ファイルからのすべての外部参照が他のオブジェクトファイルで定義されているシンボルによって満たされるようにします。
次に、それらすべてのオブジェクトファイルを結合し、各シンボルにアドレスを割り当てます。1つのオブジェクトファイルが別のオブジェクトファイルへの外部参照を持っている場合は、別のオブジェクトが使用している場所に各シンボルのアドレスを入力します。典型的なケースでは、使用される絶対アドレスのテーブルも作成するため、ローダーはファイルのロード時にアドレスを「修正」できます(つまり、それぞれにベースロードアドレスを追加します)。それらがすべて正しいメモリアドレスを参照するように、アドレス)。
かなりの数の最近のリンカは、他の "もの"のいくつか(場合によっては多く)を実行することもできます。他のモジュールがそれらを呼び出す可能性があったためですが、すべてのモジュールがまとめられると、何も呼び出さないことが明らかです)。