リンカ基本事項 リロケーションの導入まで

今回シンボルに関する基本事項とリロケーションの導入まで行っていく。

ローカルシンボルが外部ファイルから参照できないことを確認する。そのために以下のsym0.sとsym1.sという2つのアセンブリファイルを用意する。

_start:
    mov $local_symbol, %rax
    mov $global_symbol, %rax
 .global global_symbol

local_symbol:
    nop
global_symbol:
    nop

これらのファイルをコンパイルしてからリンクを試みると以下のようなエラーが発生する。

$ as sym0.s -o sym0.o
$ as sym1.s -o sym1.o
$ ld sym0.o sym1.o
ld: warning: cannot find entry symbol _start; defaulting to 0000000000400078
sym0.o: In function `_start':
(.text+0x3): undefined reference to `local_symbol'

まず一つ目の警告について、エントリーシンボルである_startシンボルがないと言っている。プログラムは基本的に_startというシンボルから始まることが決まっている。つまり最初はプログラムをメモリ上にロードするブートローダが動くようになっており、そのブートローダのプログラムに普通は_startシンボルが記述されている。普通にgccでCソースコードから実行ファイルを作るときは、自動的にブートローダプログラムもリンクしてくれるのだが、今回はldコマンドでブートローダプログラムを指定していないので、_startシンボルが見つかっていない。ブートローダプログラムはcrt.Sというファイルであることが多い。

実際、gcc -v ret.cを実行すると、リンクのフェイズで/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../lib64/crt1.oというファイルが最初にリンク対象ファイルに持ってこられていて、この中身を以下のコマンドで見てみると、確かに_startシンボルが存在していることがわかる。

$ objdump -d /usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../lib64/crt1.o
Disassembly of section .text:

0000000000000000 <_start>:
   0:   31 ed                   xor    %ebp,%ebp
   2:   49 89 d1                mov    %rdx,%r9
   5:   5e                      pop    %rsi
   6:   48 89 e2                mov    %rsp,%rdx
   9:   48 83 e4 f0             and    $0xfffffffffffffff0,%rsp
   d:   50                      push   %rax
   e:   54                      push   %rsp
   f:   49 c7 c0 00 00 00 00    mov    $0x0,%r8
  16:   48 c7 c1 00 00 00 00    mov    $0x0,%rcx
  1d:   48 c7 c7 00 00 00 00    mov    $0x0,%rdi
  24:   e8 00 00 00 00          callq  29 <_start+0x29>
  29:   f4                      hlt

もう一つこれはエラーだが、local_symbolというシンボルが定義されていないというエラーを出している。これは今回local_symbol.global ~のように定義しておらずローカルシンボルとなっており、別のファイルからは参照できないからである。つまりこのように外部のシンボル(ラベル)に飛びたい時は、グローバルシンボルとして定義する必要があることがわかる。

ここまででラベルについて述べてきたが、ここからはリロケーションについて述べていく。オブジェクトファイルには「セクションXのシンボルYをオフセットZで埋めてね」というようなリロケーション情報が含まれている。このリロケーション情報をもとにリンカはバイナリパッチを行い、プログラムのメモリ上の配置を決める。具体的にメモリ上のどこに配置するかはリンカによって決まっている。

リロケーションを理解するために以下のreloc.s, reloc_labe.sという二つのアセンブリファイルを用意する。

 .globl    _start
_start:
    movl $ref_32bit, %eax # 32bitラベルを参照
    movw $ref_16bit, %ax    # 16bitラベルを参照
    movb $ref_8bit, %al   # 8bitラベルを参照
    jmp  ref_as_jmp_label # PC 相対アドレスを参照

    movl $ref_32bit + 32, %eax # オフセット付き

    mov  $60, %rax
    syscall
 .text
    .globl ref_32bit
    .globl ref_16bit
    .globl ref_8bit
    .globl ref_as_jmp_label

    nop
ref_32bit:
    nop
ref_16bit:
    nop
ref_8bit:
    nop
ref_as_jmp_label:
    nop

二つの目のファイルであるreloc_label.sに、.textというセクションがあるが、これはプログラムのテキストである命令を表すセクションである。セクションについては、ここによくまとまっっている。

これらのファイルをオブジェクトファイルに変換した後、リロケーション情報について見てみる。

$ readelf -r reloc.o
Relocation section '.rela.text' at offset 0x170 contains 5 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
000000000001  00050000000a R_X86_64_32       0000000000000000 ref_32bit + 0
000000000007  00060000000c R_X86_64_16       0000000000000000 ref_16bit + 0
00000000000a  00070000000e R_X86_64_8        0000000000000000 ref_8bit + 0
000000000011  00050000000a R_X86_64_32       0000000000000000 ref_32bit + 20
00000000000c  000800000002 R_X86_64_PC32     0000000000000000 ref_as_jmp_label - 4
$ readelf -r reloc_label.o

There are no relocations in this file.

このようにmovなどの命令を吐いているreloc.oの方はリロケーションを行う必要があるので、readelf -rコマンドによりリロケーション情報を出力している。

次回以降リロケーションについて深く調査していく。

参照