« ^ »

GCCのビルドを順を追って確認する

所要時間: 約 6分

私がプログラムに触れ始めた頃には、家にパソコンがあったし、既にインターネットがあった。GCCもGNU/Linuxも既にあったし、その頃はあまりわかっていなかったけれど、Javaとオブジェクト指向が人気だったように思う。コンパイルやビルドの仕組みもある程度は整備されていたように思う。とあるきっかけで知人に勧められたVineLinuxのインストーラをCDに焼いて、雑にインストールしたVineLinuxを使ったりした記憶がある。もう、本当にそうだったのかあまり記憶にないけれど。

C言語の入門書を開いて「Hello world!」を表示する簡単なプログラムを書いた。

#include <stdio.h>

int main () {
    printf("hello world!\n");
}

おそらく既に入っいていたか、何らかの方法でインストールしたGCCを使い、書いたプログラムを実行可能な形式のファイルにビルドした。

gcc main.c

ソースコードは変換され、実行可能なファイルが a.out という名前で作成された。このファイルを実行すると画面にhello world!という文章を見ることができた。

./a.out
hello world!

難しくないんだなという印象とともに、だから何なんだろうという気にもなった。この文字が表示できたから一体何になるというのか、何の役に立つのかわからなかった。画面に表示されている「hello world!」という文字の延長線上に、誰かの役に立つような便利なプログラムがあるようにも思えなかった。それから暫くして、プログラムを書く事が僕の仕事になった。何で動いているのかよくわからないものを、その使い方だけを覚えて、目的を達成するという事をするようになった。時折今でもあの時の「hello world!」の文字と、いたたまれない気持ちが頭をちらつく。できる事は増えたけれど、よくわかっていないという事は変わっていない。何で動いているのかを理解していく事は、結構大変な作業で、ある程度の所で折り合いを付けて、"これはこんなふうに動くもの"という認識を前提にして進んでいく方が効率が良いし、それ以上掘り下げる必要に迫られる事も少ない。掘り下げる所とそれを諦める所の境界を、どのように決めるのかと聞かれると結構困る。ある人はライブラリやフレームワークの使い方さえ知っていれば十分だというかもしれないし、アルゴリズムについて理解を深めた方が良いという人がいるかもしれなし、そんな事はどうでもよくて、どこにどのような形式でデータが保持されるかを理解するべきだという人もいそうだ。たぶん正解はないのだろう。正解は自分で決める必要がある。

私にとってよくわかっていない事は本当に沢山あるのだけれど、その中の1つにコンパイラがある。コンパイラの動作原理が理解できていないとか、そんな難しい話じゃなくて、コンパイラがどんな順序を踏んで処理を行っているのかという簡単な事さえわかっていない。「コンパイルしてオブジェクトコードになった後、リンカーによってリンクされ、実行可能なファイルになるんだよ」という雑な知識があるだけだ。今の時代、大抵それでも困らない。それでも、どこかモヤモヤする。今回はそんなモヤモヤを解消するために、ソースから実行可能ファイルを生成する事を、段階を追って行ってみたいと思う。

最小のプログラム

最小のプログラムをC言語で書いてみる。「hello world!」を画面に表示するなんてケチ臭い事は言わない。何もしないプログラムを書く。ヘッダをインクルードするような所謂"オマジナイ"も必要ない1

環境

環境はmacOS、ビルドにはHomebrewでインストールしたGCCを使う事にする。そのためgcc-12というコマンドになっている。使用バージョンがわかりやすくなるため、このままの名前とする。ただし、GCCはmacOS用のリンカーをサポートしないため、ldコマンドについては標準の/usr/bin/ldを使用する。

ビルドして実行を確認する

まずビルド2してみる。

gcc-12 small.c

ビルドに成功すると a.out というファイルが生成される。このファイルを実行する。

./a.out

実行すると何もせずプログラムは終了する。何かを画面へ表示する事もしない。

プリプロセス

gcc-12 -E small.c

small.iが生成される。

狭義のコンパイル

C言語のコードをコンパイルしアセンブリ言語へと変換する。

gcc-12 -S small.i

small.sが作成される。

アセンブル - アセンブリコードを機械語に変換する

アセンブリ言語に変換したファイルを元にオブジェクトファイルを生成する。

gcc-12 -c small.s

small.oが作成される。このファイルはバイナリファイルである為、そのままではここに掲載できない。hexdumpで表示すると次のようになる。

hexdump small.o
0000000 facf feed 0007 0100 0003 0000 0001 0000
0000010 0004 0000 01b8 0000 2000 0000 0000 0000
0000020 0019 0000 0138 0000 0000 0000 0000 0000
0000030 0000 0000 0000 0000 0000 0000 0000 0000
0000040 0068 0000 0000 0000 01d8 0000 0000 0000
0000050 0068 0000 0000 0000 0007 0000 0007 0000
0000060 0003 0000 0000 0000 5f5f 6574 7478 0000
0000070 0000 0000 0000 0000 5f5f 4554 5458 0000
0000080 0000 0000 0000 0000 0000 0000 0000 0000
0000090 0008 0000 0000 0000 01d8 0000 0004 0000
00000a0 0000 0000 0000 0000 0400 8000 0000 0000
00000b0 0000 0000 0000 0000 5f5f 6f63 706d 6361
00000c0 5f74 6e75 6977 646e 5f5f 444c 0000 0000
00000d0 0000 0000 0000 0000 0008 0000 0000 0000
00000e0 0020 0000 0000 0000 01e0 0000 0003 0000
00000f0 0240 0000 0001 0000 0000 0200 0000 0000
0000100 0000 0000 0000 0000 5f5f 6865 665f 6172
0000110 656d 0000 0000 0000 5f5f 4554 5458 0000
0000120 0000 0000 0000 0000 0028 0000 0000 0000
0000130 0040 0000 0000 0000 0200 0000 0003 0000
0000140 0000 0000 0000 0000 000b 6800 0000 0000
0000150 0000 0000 0000 0000 0032 0000 0018 0000
0000160 0001 0000 0000 000c 0300 000c 0000 0000
0000170 0002 0000 0018 0000 0248 0000 0001 0000
0000180 0258 0000 0008 0000 000b 0000 0050 0000
0000190 0000 0000 0000 0000 0000 0000 0001 0000
00001a0 0001 0000 0000 0000 0000 0000 0000 0000
00001b0 0000 0000 0000 0000 0000 0000 0000 0000
*
00001d0 0000 0000 0000 0000 4855 e589 c031 c35d
00001e0 0000 0000 0000 0000 0008 0000 0000 0100
00001f0 0000 0000 0000 0000 0000 0000 0000 0000
0000200 0014 0000 0000 0000 7a01 0052 7801 0110
0000210 0c10 0807 0190 0000 0024 0000 001c 0000
0000220 ffb8 ffff ffff ffff 0008 0000 0000 0000
0000230 4100 100e 0286 0d43 0006 0000 0000 0000
0000240 0000 0000 0001 0600 0001 0000 010f 0000
0000250 0000 0000 0000 0000 5f00 616d 6e69 0000
0000260

オブジェクトファイルの中身を確認したい場合には、objdumpコマンドを用いる事もできる。

objdump -D small.o

small.o:	file format mach-o 64-bit x86-64

Disassembly of section __TEXT,__text:

0000000000000000 <_main>:
       0: 55                           	pushq	%rbp
       1: 48 89 e5                     	movq	%rsp, %rbp
       4: 31 c0                        	xorl	%eax, %eax
       6: 5d                           	popq	%rbp
       7: c3                           	retq

Disassembly of section __LD,__compact_unwind:

0000000000000008 <__compact_unwind>:
		...
      10: 08 00                        	orb	%al, (%rax)
      12: 00 00                        	addb	%al, (%rax)
      14: 00 00                        	addb	%al, (%rax)
      16: 00 01                        	addb	%al, (%rcx)
		...

Disassembly of section __TEXT,__eh_frame:

0000000000000028 <__eh_frame>:
      28: 14 00                        	adcb	$0, %al
      2a: 00 00                        	addb	%al, (%rax)
      2c: 00 00                        	addb	%al, (%rax)
      2e: 00 00                        	addb	%al, (%rax)
      30: 01 7a 52                     	addl	%edi, 82(%rdx)
      33: 00 01                        	addb	%al, (%rcx)
      35: 78 10                        	js	0x47 <__eh_frame+0x1f>
      37: 01 10                        	addl	%edx, (%rax)
      39: 0c 07                        	orb	$7, %al
      3b: 08 90 01 00 00 24            	orb	%dl, 603979777(%rax)
      41: 00 00                        	addb	%al, (%rax)
      43: 00 1c 00                     	addb	%bl, (%rax,%rax)
      46: 00 00                        	addb	%al, (%rax)
      48: b8 ff ff ff ff               	movl	$4294967295, %eax       ## imm = 0xFFFFFFFF
      4d: ff ff                        	<unknown>
      4f: ff 08                        	decl	(%rax)
		...
      59: 41 0e                        	<unknown>
      5b: 10 86 02 43 0d 06            	adcb	%al, 101532418(%rsi)
      61: 00 00                        	addb	%al, (%rax)
      63: 00 00                        	addb	%al, (%rax)
      65: 00 00                        	addb	%al, (%rax)
      67: 00                           	<unknown>

リンク

オブジェクトファイルをリンクする。手作業でリンクをする場合、オプションを指定する必要がある。指定しないとリンクエラーとなる。

ld small.o
ld: dynamic main executables must link with libSystem.dylib for architecture x86_64

必要なオプションを指定し、再度リンクを行う。

ld -lSystem -syslibroot /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk small.o

成功すると特に何も表示されずa.outというファイルが生成される。

実行する

生成されたa.outは実行可能ファイルの形式になっている為、実行できる。

./a.out

ただしこのプログラムは何もしないプログラムのため、何事もなかったかのように終了する。


1

まずオマジナイで意味不明さを感じる。たぶん初心者にはこういったオマジナイを気にしないで進んでいける能力が求められる。よくわからない事をよくわからないままにして進んでいかなくてはいけないし、常にそういう能力は求められるように思う。

2

ここではソースコードをコンパイルし、リンカーによってリンク処理を行い、OS上で実行可能な形式のファイルを生成する事を「ビルドする」と表現する。