リンカ基本事項 リロケーションの導入まで
今回シンボルに関する基本事項とリロケーションの導入まで行っていく。
ローカルシンボルが外部ファイルから参照できないことを確認する。そのために以下の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
コマンドによりリロケーション情報を出力している。
次回以降リロケーションについて深く調査していく。