42

C++ リンクは実際にはどのように機能しますか? 私が探しているのは、リンクがどのように行われるかについての詳細な説明であり、どのコマンドがリンクを行うかではありませ

あまり詳細には触れていない、コンパイルに関する同様の質問が既にあります: How does the compilation/linking process work?

4

3 に答える 3

70

編集:この回答を複製に移動しました: https://stackoverflow.com/a/33690144/895245

この回答は、リンクの重要な機能の 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 -felf64 hello_world.asm            # creates hello_world.o
ld -o hello_world.out hello_world.o     # static ELF executable with no libraries

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-05-28T13:17:52.953 に答える
10

実際、リンクは比較的簡単だと言えます。

最も単純な意味では、それぞれのソースに含まれる関数/グローバル/データのそれぞれに対して発行されたアセンブリが既に含まれているオブジェクト ファイル1をまとめることです。リンカーはここで非常に愚かで、すべてをシンボル(名前) とその定義 (またはコンテンツ)として扱うだけです。

明らかに、リンカーは特定の形式 (Unix では一般的に ELF 形式) を尊重し、コード/データのさまざまなカテゴリをファイルのさまざまなセクションに分離するファイルを生成する必要がありますが、それは単なるディスパッチです。

私が知っている2つの合併症は次のとおりです。

  • シンボルの重複除去の必要性: 一部のシンボルは複数のオブジェクト ファイルに存在し、作成されるライブラリ/実行可能ファイルには 1 つだけを作成する必要があります。定義の1つだけを含めるのはリンカの仕事です

  • リンク時の最適化: この場合、オブジェクト ファイルには出力されたアセンブリではなく中間表現が含まれ、リンカーはすべてのオブジェクト ファイルをマージし、最適化パス (インライン化など) を適用し、これをアセンブリにコンパイルして、最終的にその結果を出力します。 .


1 : さまざまな翻訳単位のコンパイルの結果 (大まかに言えば、前処理されたソース ファイル)

于 2012-08-25T13:47:13.927 に答える
9

すでに述べた「リンカーとローダー」に加えて、実際の最新のリンカーがどのように機能するかを知りたい場合は、ここから始めることができます。

于 2012-08-25T23:42:17.793 に答える