54
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

posted at

updated at

リンカ&ローダーの役割 自分メモメモ

参考

概要

リンカ&ローダーの仕組みについて上記本を見ながら学習した備忘録です

用語など

リンクとは

  • アセンブラされた複数のオブジェクトファイル(.o)を結合する作業のこと。結合時にオブジェクトファイルの関数呼び出し箇所を実際の関数呼び出しのメモリ番地に置換する(結合するまでは関数呼び出しなどは関数呼び出ししますよのマークがされた状態になっている)。リンクすることで実行形式ファイルが作成される。
  • リンクを行うアプリケーションのことをリンカと呼ぶ

ロードとは

  • 実行形式を読み込み、プログラムヘッダをに従ってメモリ上に配置(仮想メモリにマッピング)
  • BSS領域のゼロクリア
  • レジスタやスタック、引数の設定
  • エントリポイントにジャンプ(プログラムカウントをエントリポイントに設定)
  • ロードを行うアプリケーションのことをローダと呼ぶ

リンカもローダもOSの機能

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

  • アセンブルされたバイナリでELF形式
  • .text、.rodata、.data、.bssセクションなどなどのセクションをもつ。アセンブラでは各セクションを作成する役割がある。自分でアセンブラプログラミングする時は明示的に指定するセクションは.text、.rodata、.data、.bssセクションくらいだと思っている。他セクションはアセンブラが作成してくれる。各セクションの情報を持つセクションヘッダをもつ。
  • 各オブジェクトファイルをリンクすることで実行形式が作成される

リンカスクリプト

  • .text .dataなど各領域のメモリ配置の情報が書かれている
  • リンカが実行形式を作成する際に参照して、実行形式のプログラムヘッダなどを作成する
  • 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) }
・
・
・

実行形式(.out)

  • ELF形式
  • 各オブジェクトファイルのセクションがセグメントにマージされる
  • プログラムヘッダに記載される情報(一部)
    • ロード対象フラグ
    • ロード先の仮想アドレス
    • セグメントのサイズ など
  • ローダはプログラムヘッダを見て、各セグメントを仮想メモリにマッピングしていく

リンカについて

リンク動作順番

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

ローダー

リンカが作成した実行形式をメモリ上に展開する(仮想メモリにマッピング)

補足

シンボルテーブル(.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は共有ライブラリで実行時にアドレスが決定するので空欄。

再配置情報

オブジェクトファイルはリンク前の段階なので変数、関数を参照する箇所は空欄になっている(リンク前でアドレスを決められないので)。この空欄のことを再配置情報と呼ぶ。.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
Sign upLogin
54
Help us understand the problem. What are the problem?