リンカによるシンボル解決(1)ファイル内変数のシンボル解決(前編)

以下は、あるオブジェクトファイルの機械語コードのダンプです。

$ gcc -c sample.c -Wall
$ objdump -d sample.o -M intel
(略)
00000000 <main>:
   0:	55                   	push   ebp
   1:	89 e5                	mov    ebp,esp
   3:	8b 15 04 00 00 00    	mov    edx,DWORD PTR ds:0x4
   9:	a1 08 00 00 00       	mov    eax,ds:0x8
   e:	01 d0                	add    eax,edx
  10:	5d                   	pop    ebp
  11:	c3                   	ret   

これをコンパイルしてリンクが完了すると、以下のコードに変換されます。

$ gcc sample.o -o sample -wall
$ objdump -d sample -M intel
(略)
080483b4 <main>:
 80483b4:	55                   	push   ebp
 80483b5:	89 e5                	mov    ebp,esp
 80483b7:	8b 15 28 a0 04 08    	mov    edx,DWORD PTR ds:0x804a028
 80483bd:	a1 18 a0 04 08       	mov    eax,ds:0x804a018
 80483c2:	01 d0                	add    eax,edx
 80483c4:	5d                   	pop    ebp
 80483c5:	c3                   	ret

リンクの前後で変化のあった行を抜粋すると…

/* リンク前 */
   3:	8b 15 04 00 00 00    	mov    edx,DWORD PTR ds:0x4
   9:	a1 08 00 00 00       	mov    eax,ds:0x8
/* リンク後 */
 80483b7:	8b 15 28 a0 04 08    	mov    edx,DWORD PTR ds:0x804a028
 80483bd:	a1 18 a0 04 08       	mov    eax,ds:0x804a018

リンク前とリンク後で、2つのmov命令のオペランドが変化しています。

  1. 「04 00 00 00」→「28 a0 04 08」
  2. 「08 00 00 00」→「18 a0 04 08」

エンディアンを考慮すると、リンク後の値は「0x0804a028」「0x804a018」となっているわけですが、これは変数のアドレスですね。つまり、機械語コードの中で、変数のアドレスが埋め込まれる予定の部分には、リンク前には「04」「08」という仮の値(?)が埋め込まれており、リンクによって、その「仮の値」が実際の論理アドレスに置換されるのだと推測されます。

イメージで言うと、リンク前のアドレス埋め込み部分は空欄になっていますが、リンカによるシンボル解決によって、空欄だった部分に、実際の論理アドレスが埋め込まれるわけです。

Cのソースコード
   |
(コンパイル)
   |
(アセンブル)
   ↓
mov edx,[    ]
mov eax,[    ]
   |
 (リンク)
   ↓
mov edx,DWORD PTR ds:0x804a028
mov eax,ds:0x804a018

しかし、機械語コードの中にアドレスを埋め込む部分は、どうやって管理されているのでしょうか。リンカは、オブジェクトファイルに書かれた機械語の意味を理解せず、単純にベタバイナリ(16進数の羅列)のデータとしてしか見てないのに、どうやって埋め込み部分を特定しているのでしょうか。

つづく…