Help us understand the problem. What is going on with this article?

リンカの役割 自分メモメモ

More than 3 years have passed since last update.

リンカについて

ソースコードから実行形式を作成するまでの流れ
を書いた時にリンカに触れたので、リンカ周りの用語などを自分の言葉でざっとまとめて復習してみた。

用語

リンカ動作順番

  1. 各オブジェクトファイルのセクションをマージしてまとめる。そしてマージしたセクション同士を意味のある単位でセグメント(ローダーがメモリにロードする単位)にまとめる。
  2. 関数、変数の実体が存在する各セクションをメモリのどの番地に配置されるかリンカスクリプトを参照して決める。(再配置)
  3. 再配置されて変数、関数のアドレスが決まったのでシンボルテーブル(.symtabセクション)のアドレス欄を埋める。
  4. シンボルテーブルを元に各セクションで空欄(再配置情報)になっていた箇所にアドレスを補充。 (名前解決=シンボル解決)
  5. 実行形式として出力する。

シンボル

関数名、変数名はシンボルという。

オブジェクトファイル .o

アセンブラがアセンぶりされたバイナリでELF形式。.text、.rodata、.data、.bssセクションなどなどのセクションをもつ。なのでアセンブラでは各セクションを作成する役割がある。自分でアセンブラプログラミングする時は明示的に指定するのは.text、.rodata、.data、.bssセクションくらいのはず。ほかはアセンブラが作成してくれるはず。各セクションの情報を持つセクションヘッダをもつ。

シンボルテーブル(.symtabセクション)

関数・変数が配置されるメモリ上のアドレスとシンボル名の表。オブジェクトファイルはシンボルを管理するシンボルテーブルを持っている。.symtabセクションに入っている。

実行形式のシンボルテーブル

# nm a.out 
・
・
0000000000400440 T _start
0000000000601034 b completed.6337
0000000000601030 W data_start
0000000000400470 t deregister_tm_clones
0000000000400500 t frame_dummy
0000000000400530 T main
                 U printf@@GLIBC_2.2.5
00000000004004a0 t register_tm_clones

リンク後はmainにアドレスが割り当てられていて、printは共有ライブラリで実行時にアドレスが決定するので空欄。

再配置

関数や変数その他をメモリ上の番地に割り当てる作業のこと。ただ実際にメモリにロードするのはローダーの役割なので、メモリ上のどの番地に割り当てるかだけを決めてプログラムヘッダに書き込んでいる(リンカスクリプトをみて決める)。ローダーはそれをみて実際にメモリ上にロードする。

リンカスクリプト

再配置はリンカスクリプトを参照して行う。メモリマッピングの方法、エントリポイントや各セクションのまとめかたが書かれている。GNUldでは実行時にリンカスクリプトを指定しないとldをビルドする時に組み込まれたデフォルトのリンカスクリプトが利用される。ld --verboseで確認できる。通常何も指定しないので下のデフォルトのリンカスクリプトを利用している。

# ld --verbose
GNU ld version 2.23.52.0.1-30.el7_1.2 20130226
  サポートされているエミュレーション:
   elf_x86_64
   elf32_x86_64
   elf_i386
   i386linux
   elf_l1om
   elf_k1om
内部リンカスクリプトを使用しています:
==================================================
/* Script for -z combreloc: combine and sort reloc sections */
OUTPUT_FORMAT("elf64-x86-64", "elf64-x86-64",
              "elf64-x86-64")
OUTPUT_ARCH(i386:x86-64)
ENTRY(_start)
SEARCH_DIR("/usr/x86_64-redhat-linux/lib64"); SEARCH_DIR("/usr/local/lib64"); SEARCH_DIR("/lib64"); SEARCH_DIR("/usr/lib64"); SEARCH_DIR("/usr/x86_64-redhat-linux/lib"); SEARCH_DIR("/usr/lib64"); SEARCH_DIR("/usr/local/lib"); SEARCH_DIR("/lib"); SEARCH_DIR("/usr/lib");
SECTIONS
{
  /* Read-only sections, merged into text segment: */
  PROVIDE (__executable_start = SEGMENT_START("text-segment", 0x400000)); . = SEGMENT_START("text-segment", 0x400000) + SIZEOF_HEADERS;
  .interp         : { *(.interp) }
  .note.gnu.build-id : { *(.note.gnu.build-id) }
  .hash           : { *(.hash) }
  .gnu.hash       : { *(.gnu.hash) }
・
・
・

再配置情報

オブジェクトファイルはリンク前の段階なので変数、関数を参照する箇所は空欄になっている(リンク前でアドレスをキメようがない)。この空欄のことを再配置情報と呼ぶ。.relxxx&.relaxxxに入ってる。xxxは各セクション名。この二つは再配置情報の格納方法が異るみたい。

  • 再配置情報は空欄ごとにあるので、関数や変数を参照する数だけある。
  • シンボルテーブルは関数、変数の定義の数だけ。

再配置情報を見てみる

# readelf -r hello.o

再配置セクション '.rela.text' (オフセット 0x600) は 10 個のエントリから構成されています:
  オフセット      情報           型             シンボル値    シンボル名 + 加数
000000000023  00050000000a R_X86_64_32       0000000000000000 .rodata + 0
00000000002d  000a00000002 R_X86_64_PC32     0000000000000000 printf - 4
000000000037  00050000000a R_X86_64_32       0000000000000000 .rodata + 0
000000000041  000a00000002 R_X86_64_PC32     0000000000000000 printf - 4
00000000004b  00050000000a R_X86_64_32       0000000000000000 .rodata + 0
000000000055  000a00000002 R_X86_64_PC32     0000000000000000 printf - 4
00000000005f  00050000000a R_X86_64_32       0000000000000000 .rodata + 0
000000000069  000a00000002 R_X86_64_PC32     0000000000000000 printf - 4
00000000006e  00050000000a R_X86_64_32       0000000000000000 .rodata + 3
000000000078  000a00000002 R_X86_64_PC32     0000000000000000 printf - 4

printfが複数ある。printfを参照している数だけある。

# nm hello.o
0000000000000000 T main
                 U printf

シンボルテーブルは定義の数のみ。

リンカの実体

ソースコードから実行形式を作成するまでの流れで書いたように、CentOS7のGCCの場合だと/usr/libexec/gcc/x86_64-redhat-linux/4.8.3/collect2ただcollcect2はGNUのldである/usr/bin/ldを使っている。LinuxではGNUのldが一般的みたい。ただし、リンカはOSに強く依存するものなのでOS毎にリンカが存在している。Microsoftではlink、solarisならsolarisのld。なのでGCCは環境に応じてリンカを使い分けるので、collect2はその共通のラッパーになっている。

実行形式

これもELF形式。各セクションを意味的にまとめたプログラムヘッダが作られる。
プログラムヘッダをみてみた。

# readelf -l a.out 

Elf ファイルタイプは EXEC (実行可能ファイル) です
エントリポイント 0x400440
9 個のプログラムヘッダ、始点オフセット 64

プログラムヘッダ:
  タイプ        オフセット          仮想Addr           物理Addr
            ファイルサイズ        メモリサイズ         フラグ 整列
  PHDR           0x0000000000000040 0x0000000000400040 0x0000000000400040
                 0x00000000000001f8 0x00000000000001f8  R E    8
  INTERP         0x0000000000000238 0x0000000000400238 0x0000000000400238
                 0x000000000000001c 0x000000000000001c  R      1
      [要求されるプログラムインタプリタ: /lib64/ld-linux-x86-64.so.2]
  LOAD           0x0000000000000000 0x0000000000400000 0x0000000000400000
                 0x000000000000077c 0x000000000000077c  R E    200000
  LOAD           0x0000000000000e10 0x0000000000600e10 0x0000000000600e10
                 0x0000000000000224 0x0000000000000228  RW     200000
  DYNAMIC        0x0000000000000e28 0x0000000000600e28 0x0000000000600e28
                 0x00000000000001d0 0x00000000000001d0  RW     8
  NOTE           0x0000000000000254 0x0000000000400254 0x0000000000400254
                 0x0000000000000044 0x0000000000000044  R      4
  GNU_EH_FRAME   0x0000000000000654 0x0000000000400654 0x0000000000400654
                 0x0000000000000034 0x0000000000000034  R      4
  GNU_STACK      0x0000000000000000 0x0000000000000000 0x0000000000000000
                 0x0000000000000000 0x0000000000000000  RW     10
  GNU_RELRO      0x0000000000000e10 0x0000000000600e10 0x0000000000600e10
                 0x00000000000001f0 0x00000000000001f0  R      1

 セグメントマッピングへのセクション:
  セグメントセクション...
   00     
   01     .interp 
   02     .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt .init .plt .text .fini .rodata .eh_frame_hdr .eh_frame 
   03     .init_array .fini_array .jcr .dynamic .got .got.plt .data .bss 
   04     .dynamic 
   05     .note.ABI-tag .note.gnu.build-id 
   06     .eh_frame_hdr 
   07     
   08     .init_array .fini_array .jcr .dynamic .got

00~08が各セクションを意味的にまとめている番号。上と下は順番が対応している。LOADはローダーがメモリにロードする箇所。オフセットはELFの先頭からの位置。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした