170

私はいつも疑問に思っていました。コンパイラがあなたが書いたコードをバイナリに変換することは知っていますが、リンカは何をしますか? 彼らはいつも私にとって謎でした。

「リンク」とは何かを大まかに理解しています。ライブラリとフレームワークへの参照がバイナリに追加されるときです。それ以上のことはわかりません。私にとって、それは「うまくいく」だけです。動的リンクの基本も理解していますが、深すぎることはありません。

誰かが用語を説明できますか?

4

4 に答える 4

207

リンカを理解するには、ソース ファイル (C または C++ ファイルなど) を実行可能ファイル (実行可能ファイルとは、マシン上で実行できるファイルまたは同じマシン アーキテクチャを実行している他の誰かのマシン)。

内部的には、プログラムがコンパイルされると、コンパイラはソース ファイルをオブジェクト バイト コードに変換します。このバイト コード (オブジェクト コードと呼ばれることもあります) は、コンピュータ アーキテクチャだけが理解できるニーモニック命令です。従来、これらのファイルには .OBJ 拡張子が付いています。

オブジェクト ファイルが作成されると、リンカーが機能します。多くの場合、何か役に立つことを行う実際のプログラムは、他のファイルを参照する必要があります。たとえば、C では、自分の名前を画面に出力する単純なプログラムは次のようになります。

printf("Hello Kristina!\n");

コンパイラがプログラムを obj ファイルにコンパイルするとき、printf関数への参照を配置するだけです。リンカはこの参照を解決します。ほとんどのプログラミング言語には、その言語に期待される基本的な機能をカバーするルーチンの標準ライブラリがあります。リンカーは、OBJ ファイルをこの標準ライブラリにリンクします。リンカーは、OBJ ファイルを他の OBJ ファイルとリンクすることもできます。別の OBJ ファイルから呼び出すことができる関数を持つ別の OBJ ファイルを作成できます。リンカは、ワード プロセッサのコピー アンド ペーストのように機能します。プログラムが参照するすべての必要な関数を「コピー」し、単一の実行可能ファイルを作成します。コピーされた他のライブラリが、さらに他の OBJ またはライブラリ ファイルに依存している場合があります。リンカは、仕事をするためにかなり再帰的にならなければならないことがあります。

すべてのオペレーティング システムが単一の実行可能ファイルを作成するわけではないことに注意してください。たとえば、Windows では、これらすべての機能を 1 つのファイルにまとめる DLL が使用されています。これにより、実行可能ファイルのサイズが縮小されますが、実行可能ファイルはこれらの特定の DLL に依存するようになります。DOS は、オーバーレイ (.OVL ファイル) と呼ばれるものを使用していました。これには多くの目的がありましたが、1 つは一般的に使用される機能を 1 つのファイルにまとめることでした (別の目的は、ご参考までに、大規模なプログラムをメモリに収めることができるようにすることでした。DOS はメモリに制限があり、オーバーレイはメモリから「アンロード」され、他のオーバーレイはそのメモリの上に「ロード」される可能性があるため、「オーバーレイ」と呼ばれます)。Linux には共有ライブラリがありますが、これは基本的に DLL と同じ考え方です (私が知っている筋金入りの Linux 関係者は、多くの大きな違いがあると教えてくれます)。

これが理解に役立つことを願っています!

于 2010-07-23T23:04:48.383 に答える
118

アドレス移転の最小限の例

アドレスの再配置は、リンクの重要な機能の 1 つです。

それでは、最小限の例でどのように機能するかを見てみましょう。

0) はじめに

概要: 再配置は、.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 で。

1) .o の .text

まず.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レジスタに移動され、write システム コールに渡されます。

ちょっと待って!"Hello world!"プログラムがロードされたときにメモリ内の最終的な場所をコンパイラが知るにはどうすればよいでしょうか?

特に、多数のファイルを複数のセクション.oと一緒にリンクした後は、それはできません。.data

リンカだけがこれらのオブジェクト ファイルをすべて持つので、リンカだけがそれを行うことができます。

したがって、コンパイラは次のようになります。

  • 0x0コンパイルされた出力にプレースホルダー値を置きます
  • 適切なアドレスを使用してコンパイル済みコードを変更する方法について、リンカーに追加情報を提供します。

この「追加情報」は.rela.text、オブジェクト ファイルのセクションに含まれています。

2) .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,%rsix86-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 00at アドレス0xC

    • Calculation = S + A

      • Sは、再配置されるアドレスの値です。したがって、00 00 00 00 00 00 00 00
      • Aここにある加数0です。これは移転エントリのフィールドです。

      そのS + A == 0ため、セクションの最初のアドレスに移動します.data

3) .out の .text

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 文字列だけです。

ボーナスレベル

于 2015-11-13T10:04:02.467 に答える
18

'C'のような言語では、コードの個々のモジュールは、従来、オブジェクトコードのブロブに個別にコンパイルされます。これは、モジュールがそれ自体の外部(つまり、ライブラリまたは他のモジュール)で行うすべての参照以外のあらゆる点で実行する準備ができています。まだ解決されていません(つまり、誰かがやって来てすべての接続を確立するまで、空白になっています)。

リンカが行うことは、すべてのモジュールを一緒に調べ、各モジュールがそれ自体の外部に接続するために必要なものを調べ、エクスポートしているすべてのものを調べることです。次に、それをすべて修正し、実行可能な最終的な実行可能ファイルを生成します。

ダイナミックリンクも行われている場合、リンカーの出力はまだ実行できません-まだ解決されていない外部ライブラリへの参照がいくつかあり、アプリをロードするときにOSによって解決されます(またはおそらく実行中も後で)。

于 2010-07-23T22:48:43.410 に答える
13

コンパイラがオブジェクト ファイルを生成すると、そのオブジェクト ファイルで定義されているシンボルのエントリと、そのオブジェクト ファイルで定義されていないシンボルへの参照が含まれます。リンカはそれらを取得してまとめ、(すべてが正常に機能する場合) 各ファイルからのすべての外部参照が、他のオブジェクト ファイルで定義されているシンボルによって満たされるようにします。

次に、これらすべてのオブジェクト ファイルを結合し、各シンボルにアドレスを割り当てます。あるオブジェクト ファイルが別のオブジェクト ファイルへの外部参照を持っている場合、別のオブジェクトで使用されている各シンボルのアドレスを埋めます。典型的なケースでは、使用される絶対アドレスのテーブルも構築するため、ローダーはファイルがロードされるときにアドレスを「修正」できます/修正します(つまり、ベースロードアドレスをそれぞれに追加します)すべてが正しいメモリアドレスを参照するようにします)。

かなりの数の最新のリンカーは、すべてのモジュールが表示された場合にのみ可能な方法でコードを最適化するなど、他の「もの」の一部 (いくつかのケースでは多く) を実行することもできます (たとえば、含まれていた関数を削除するなど)。他のモジュールがそれらを呼び出す可能性があったためですが、すべてのモジュールがまとめられると、何も呼び出されないことが明らかになります)。

于 2010-07-23T23:01:27.117 に答える